Objective

To build a predictive model for attrition that provides useful input into an ROI calculation for retention intervention(s).

Summary & Conclusions

Using the provided data which included variables such as employee satisfaction levels, last evaluation score, number of projects, average hours worked in a month, tenure, promotions, and salary, we sought to find a model that could accurately predict which employees may leave the company. Given the variables in the data, our goal was to determine which predictors could provide the most signal that an employee intended to leave. If this information was known, we may be able to intervene and prevent the employee from leaving, thereby saving the company money.

See the section called Business Value: Visualizing Model Performance at the bottom of this document for visualizations of the ROI discussed here. well actually, bugs in the package prevented this little bit of magic. maybe next time

Proposed Model

The model proposed is a Random Forest model. The model uses random combinations of predictor variables to form a decision tree based prediction for the outcome variable.

The variable importance statistics for the RF model are ase follows:

Predictor        |         0 |         1 | MeanDecreaseAccuracy | MeanDecreaseGini

———————| ——— | ——— | ——————– | —————- satisfaction_level | 101.400344 | 337.898530 | 319.888350 | 1124.759934 last_evaluation | 18.640044 | 111.035368 | 113.710098 | 302.200133 number_project | 69.694218 | 239.160597 | 235.333583 | 445.560219 average_monthly_hours | 62.586243 | 101.416338 | 111.910062 | 328.637547 time_spend_company | 57.939171 | 82.418274 | 91.668575 | 459.621633 Work_accident | 4.319829 | 8.684521 | 9.092943 | 4.591932 promotion_last_5years | 2.830797 | 5.160545 | 5.064632 | 1.076596 department | 7.580276 | 48.293374 | 29.060656 | 29.775646 salary | 7.210778 | 27.537577 | 19.853285 | 13.094145

Examining these stats, we see that satisfaction_level, last_evaluation, number_project variables give the most signal (aka, have the most influence) as factors indicating an employee may leave the company. As these values increase, so too does the probability that the employee will quit. The proposed model has an accuracy of 98.4% for the test dataset.

It is important to note that this model is valid for the test data set, but may not generalize well to future data. The proposed model should be well validated against new datasets before proceeding. That said, the Random Forest approach should be more generalizable than a standard Decision Tree model based on the underlying statistical properties of the

Confidence was checked through cross-validation of 10 k-folds in the data set. To gain further confidence, future analysts may wish to increase the number of bootstrap datasets they test to increase confidence in the overall model as well as gain more signal on specific features (aka predictor variables) of the model.

ROI of Intervention

Unfortunately, Random Forests can be difficult to interpret, so there is not a direct intervention that can be proposed based on this model. However, if we focused on the leading variable - satisfaction level - and ran an intervention to improve satisfaction, we can understand how much money may be saved.

For this objective, it has been determined that the loss of an employee costs the company 100,000 dollars. We are recommending an intervention program with a cost of 5,000 dollars per employee. Based on our research and data, we expect the intervention program to work approximately 50% of the time. With these costs in mind, it is understood that the company will save 95,000 dollars per employee who does not leave as a result of the intervention.

In the final running of the model, it was accurately predicted that 1693 people would leave the company. Assuming we can save half of those people with our intervention (n=847), the company will have saved 80.4 M dollars, minus the cost of the intervention for those who weren’t saved (4.2M), a total of 76.2M dollars.

With 98.8% accuracy in the model and

Confusion Matrix for rf_final OOB estimate of error rate: 1.19%

Confusion matrix: 0 1 class.error 0 5717 9 0.001571778 1 80 1693 0.045121263

For Future Analysts Using this Code

The following code was used to run and interpret 4 models to predict attrition using the provided dataset. The first sections include exploratory data analysis and visualizations to understand the context of the data. The data was relatively “clean” with no missing values or duplicated observations. Models were developed using tidy modeling principles, including workflows and recipes. The recipes include preprocessing steps to normalize and balance the data.

A table of model results and decisions are below (more details can be found in the model_comp table). The approach attempted several models with varying levels of complexity. The simpler models did not perform well and did not predict above/beyond chance. The more complex models perform very well on the available dataset, but are difficult to interpret for the business.

CAVEAT: The proposed model should be well validated against new datasets before proceeding. The Random Forest approach should be more generalizable than a standard Decision Tree.

Model Accuracy Decision
Logistic Regression 0.7473040 Low accuracy
Support Vector Class 0.7555702 Low accuracy
Neural Net 0.9361251 High accuracy, unable to interpret results
Random Forest 0.9846636 High accuracy, unable to interpret results

File Setup

Libraries & Conflicts

Helper Functions


Loading Data

library(readxl)
Data <- read_excel("C:/Users/Jaclyn/Desktop/GithubClone/psyc6841_finalproject/01_data/PSYC6841_Final_Project_Data.xlsx")

str(Data)
tibble [9,999 x 10] (S3: tbl_df/tbl/data.frame)
 $ satisfaction_level   : num [1:9999] 0.1 1 0.69 0.63 0.88 0.8 0.11 0.85 0.37 0.99 ...
 $ last_evaluation      : num [1:9999] 0.91 0.85 0.62 0.93 0.65 0.76 0.8 0.67 0.71 0.54 ...
 $ number_project       : num [1:9999] 6 4 4 4 5 4 6 4 6 4 ...
 $ average_monthly_hours: num [1:9999] 255 156 183 238 228 135 256 228 266 239 ...
 $ time_spend_company   : num [1:9999] 4 3 4 2 3 2 4 4 5 3 ...
 $ Work_accident        : num [1:9999] 0 0 0 0 0 0 0 0 0 0 ...
 $ left                 : num [1:9999] 1 0 0 0 0 0 1 0 0 0 ...
 $ promotion_last_5years: num [1:9999] 0 0 0 0 0 0 0 0 0 0 ...
 $ sales                : chr [1:9999] "sales" "sales" "support" "technical" ...
 $ salary               : chr [1:9999] "medium" "low" "low" "low" ...

Data Preprocessing & Review

Overview

skim(Data)
-- Data Summary ------------------------
                           Values
Name                       Data  
Number of rows             9999  
Number of columns          10    
_______________________          
Column type frequency:           
  character                2     
  numeric                  8     
________________________         
Group variables            None  

-- Variable type: character -------------------------------------------------------------------------------
# A tibble: 2 x 8
  skim_variable n_missing complete_rate   min   max empty n_unique whitespace
* <chr>             <int>         <dbl> <int> <int> <int>    <int>      <int>
1 sales                 0             1     2    11     0       10          0
2 salary                0             1     3     6     0        3          0

-- Variable type: numeric ---------------------------------------------------------------------------------
# A tibble: 8 x 11
  skim_variable         n_missing complete_rate     mean     sd    p0    p25    p50    p75  p100 hist 
* <chr>                     <int>         <dbl>    <dbl>  <dbl> <dbl>  <dbl>  <dbl>  <dbl> <dbl> <chr>
1 satisfaction_level            0             1   0.611   0.250  0.09   0.44   0.64   0.82     1 ▃▅▇▇▇
2 last_evaluation               0             1   0.717   0.171  0.36   0.56   0.72   0.87     1 ▂▇▆▇▇
3 number_project                0             1   3.81    1.24   2      3      4      5        7 ▇▅▃▂▁
4 average_monthly_hours         0             1 201.     50.0   96    156    199    245      310 ▃▇▆▇▂
5 time_spend_company            0             1   3.52    1.49   2      3      3      4       10 ▇▃▁▁▁
6 Work_accident                 0             1   0.147   0.354  0      0      0      0        1 ▇▁▁▁▂
7 left                          0             1   0.236   0.425  0      0      0      0        1 ▇▁▁▁▂
8 promotion_last_5years         0             1   0.0208  0.143  0      0      0      0        1 ▇▁▁▁▁

The dataset has 9,999 rows, 10 columns, 2 factor columns, 8 numeric columns, and no missing values (n_missing) across all columns of the data frame.

glimpse(Data)
Rows: 9,999
Columns: 10
$ satisfaction_level    <dbl> 0.10, 1.00, 0.69, 0.63, 0.88, 0.80, 0.11, 0.85, 0.37, 0.99, 0.66, 0.80, 0.6~
$ last_evaluation       <dbl> 0.91, 0.85, 0.62, 0.93, 0.65, 0.76, 0.80, 0.67, 0.71, 0.54, 0.95, 0.82, 0.7~
$ number_project        <dbl> 6, 4, 4, 4, 5, 4, 6, 4, 6, 4, 5, 3, 5, 4, 3, 2, 3, 3, 3, 3, 4, 4, 3, 2, 4, ~
$ average_monthly_hours <dbl> 255, 156, 183, 238, 228, 135, 256, 228, 266, 239, 206, 218, 270, 238, 254, ~
$ time_spend_company    <dbl> 4, 3, 4, 2, 3, 2, 4, 4, 5, 3, 2, 3, 3, 10, 2, 3, 3, 5, 3, 2, 2, 4, 3, 3, 4,~
$ Work_accident         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, ~
$ left                  <dbl> 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, ~
$ promotion_last_5years <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
$ sales                 <chr> "sales", "sales", "support", "technical", "sales", "technical", "IT", "supp~
$ salary                <chr> "medium", "low", "low", "low", "medium", "low", "low", "low", "low", "low",~
colnames(Data)
 [1] "satisfaction_level"    "last_evaluation"       "number_project"        "average_monthly_hours"
 [5] "time_spend_company"    "Work_accident"         "left"                  "promotion_last_5years"
 [9] "sales"                 "salary"               
#VIEWING CHARACTER DATA VALUES
Data %>%
    select_if(is.character) %>% 
    map(unique) #from purrr -- shows us all of the values available for our character data
select_if: dropped 8 variables (satisfaction_level, last_evaluation, number_project, average_monthly_hours, time_spend_company, …)
$sales
 [1] "sales"       "support"     "technical"   "IT"          "marketing"   "product_mng" "accounting" 
 [8] "management"  "RandD"       "hr"         

$salary
[1] "medium" "low"    "high"  

The sales column appears to be mislabelled. We’ll rename it “Department” based on the values we see in that column.

#RENAME 'SALES' COLUMN TO `DEPARTMENT`
Data <- rename(Data, department = sales)
rename: renamed one variable (department)

Data Structures

#DOUBLE CHECK FACTOR
glimpse(Data)
Rows: 9,999
Columns: 10
$ satisfaction_level    <dbl> 0.10, 1.00, 0.69, 0.63, 0.88, 0.80, 0.11, 0.85, 0.37, 0.99, 0.66, 0.80, 0.6~
$ last_evaluation       <dbl> 0.91, 0.85, 0.62, 0.93, 0.65, 0.76, 0.80, 0.67, 0.71, 0.54, 0.95, 0.82, 0.7~
$ number_project        <dbl> 6, 4, 4, 4, 5, 4, 6, 4, 6, 4, 5, 3, 5, 4, 3, 2, 3, 3, 3, 3, 4, 4, 3, 2, 4, ~
$ average_monthly_hours <dbl> 255, 156, 183, 238, 228, 135, 256, 228, 266, 239, 206, 218, 270, 238, 254, ~
$ time_spend_company    <dbl> 4, 3, 4, 2, 3, 2, 4, 4, 5, 3, 2, 3, 3, 10, 2, 3, 3, 5, 3, 2, 2, 4, 3, 3, 4,~
$ Work_accident         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, ~
$ left                  <dbl> 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, ~
$ promotion_last_5years <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
$ department            <chr> "sales", "sales", "support", "technical", "sales", "technical", "IT", "supp~
$ salary                <chr> "medium", "low", "low", "low", "medium", "low", "low", "low", "low", "low",~
#TABLE OF NUMBER UNIQUE VALUES FOR NUMERIC VARIABLES
Data %>%
    select_if(is.numeric) %>%
    map_df(~ unique(.) %>% length()) %>% # tries to turn it into a df instead of a list
    gather() %>%  
  arrange(desc(value))
select_if: dropped 2 variables (department, salary)
gather: reorganized (satisfaction_level, last_evaluation, number_project, average_monthly_hours, time_spend_company, …) into (key, value) [was 1x8, now 8x2]
#VIEW DUMMY CODED VARIABLES
Data %>% 
  distinct(Work_accident, left, promotion_last_5years)
distinct: removed 9,991 rows (>99%), 8 rows remaining
#VIEW LEVELS OF SALARY
Data %>% 
  distinct(salary)
distinct: removed 9,996 rows (>99%), 3 rows remaining

The salary data needs to be turned into an ordered factor. We’ll order the levels from Low to High.

#CHANGE SALARY TO ORDERED FACTOR
Data <- Data %>%
  mutate(salary = factor(salary,
                                 levels = c("low",
                                            "medium",
                                            "high")))
mutate: converted 'salary' from character to factor (0 new NA)

Three of our numeric variables appear to be dummy coded (0=No/1=Yes), including Work_accident, left, promotion_last_5years.

One of these variables, Left, is our outcome variable, determining if someone stayed at or left the company.

Three of our numeric variables appear to be continuous, including average_monthly_hours, satisfaction_level, and last_evaluation.

Two numeric variables are discrete, including only 6 (number_project) or 8 (time_spend_company) values each.

Missingness & Duplicates

#CHECK FOR MISSING DATA
library(Amelia)
Warning: package ‘Amelia’ was built under R version 4.0.5
Loading required package: Rcpp
Warning: package ‘Rcpp’ was built under R version 4.0.5

Attaching package: ‘Rcpp’

The following object is masked from ‘package:rsample’:

    populate

## 
## Amelia II: Multiple Imputation
## (Version 1.7.6, built: 2019-11-24)
## Copyright (C) 2005-2021 James Honaker, Gary King and Matthew Blackwell
## Refer to http://gking.harvard.edu/amelia/ for more information
## 
missmap(Data, y.at=c(1), y.labels=c(''), col=c('orange', 'purple'))
Warning: Unknown or uninitialised column: `arguments`.
Warning: Unknown or uninitialised column: `arguments`.

Wow, no missing variables! We will consider our dataset whole and do not need to impute any missing data.

We’ll also check if there are any duplicate lines we need to be aware of.

#CHECK FOR ROWS WITH EXACT SAME VALUES
sum(is.na(duplicated(Data)))
[1] 0
#IS OUR ID COLUMN KOSHER?
which(duplicated(Data$ID))
Warning: Unknown or uninitialised column: `ID`.
integer(0)

There do not appear to be any duplicate rows or column data.

Summary Statistics

psych::describe(Data)

At first glance, the variables appear as expected. Points of interest: * Large, positive skew in time_spend_company, one of our discrete variables * Large SD in number_project and average_monthly_hours * Large Range in average_monthly_hours

# ATTRITION PERCENTAGE IN DATASET
Data %>%
  tabyl(left) %>% 
  adorn_pct_formatting(digits = 0, affix_sign = TRUE)
 left    n percent
    0 7635     76%
    1 2364     24%

Taking a look at our outcome variable we find that Our data is unbalanced, given about 1/4 of the observations left the organization and 3/4’s stayed. We’ll need to account for this in our recipes.

#SEVERAL TABLES TO LOOK AT VARIABLES IN ISOLATION

# CONTINUOUS VARIABLES
# tabyl(Data, satisfaction_level)
# tabyl(Data, last_evaluation)
# tabyl(Data, average_monthly_hours)

#DISCRETE & CATEGORICAL VARIABLES
tabyl(Data, number_project) %>% 
  adorn_pct_formatting(digits = 0, affix_sign = TRUE)
 number_project    n percent
              2 1593     16%
              3 2741     27%
              4 2837     28%
              5 1853     19%
              6  796      8%
              7  179      2%
tabyl(Data, time_spend_company) %>% 
  adorn_pct_formatting(digits = 0, affix_sign = TRUE)
 time_spend_company    n percent
                  2 2158     22%
                  3 4263     43%
                  4 1702     17%
                  5  992     10%
                  6  485      5%
                  7  119      1%
                  8  124      1%
                 10  156      2%
tabyl(Data, Work_accident) %>% 
  adorn_pct_formatting(digits = 0, affix_sign = TRUE)
 Work_accident    n percent
             0 8528     85%
             1 1471     15%
tabyl(Data, promotion_last_5years) %>% 
  adorn_pct_formatting(digits = 0, affix_sign = TRUE)
 promotion_last_5years    n percent
                     0 9791     98%
                     1  208      2%
tabyl(Data, department) %>% 
  adorn_pct_formatting(digits = 0, affix_sign = TRUE)
  department    n percent
  accounting  516      5%
          hr  493      5%
          IT  816      8%
  management  426      4%
   marketing  599      6%
 product_mng  589      6%
       RandD  524      5%
       sales 2754     28%
     support 1499     15%
   technical 1783     18%
tabyl(Data, salary) %>% 
  adorn_pct_formatting(digits = 0, affix_sign = TRUE)
 salary    n percent
    low 4866     49%
 medium 4301     43%
   high  832      8%

Checking the balance of predictor variables, we see:

  • There is a long tail on time_spent_company
  • Only 15% of observations experienced work_accident
  • Only 2% of observations have been promotion_last_5years
  • The department distribution is uneven, with 28% of observations showing in the Sales department. Support and Technical departments closely follow. Other departments are only 5% or 6% of the data.

Data Visualization

Data %>% 
  introduce() %>% 
  pivot_longer(everything())
pivot_longer: reorganized (rows, columns, discrete_columns, continuous_columns, all_missing_columns, …) into (name, value) [was 1x9, now 9x2]
plot_intro(Data)

#library(funModeling)

#FACTOR VARIABLE FREQUENCY
freq(Data)
Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.
Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

[1] "Variables processed: department, salary"

  • The highest percentage of people are found in Sales.
  • Low and Medium salary range are most prevalent, with High salary range accounting for only 8% of observations.
skim(Data) %>% 
  filter(skim_type == 'numeric')
filter: removed 2 rows (20%), 8 rows remaining
-- Data Summary ------------------------
                           Values
Name                       Data  
Number of rows             9999  
Number of columns          10    
_______________________          
Column type frequency:           
  numeric                  8     
________________________         
Group variables            None  

-- Variable type: numeric ---------------------------------------------------------------------------------
# A tibble: 8 x 11
  skim_variable         n_missing complete_rate     mean     sd    p0    p25    p50    p75  p100 hist 
* <chr>                     <int>         <dbl>    <dbl>  <dbl> <dbl>  <dbl>  <dbl>  <dbl> <dbl> <chr>
1 satisfaction_level            0             1   0.611   0.250  0.09   0.44   0.64   0.82     1 ▃▅▇▇▇
2 last_evaluation               0             1   0.717   0.171  0.36   0.56   0.72   0.87     1 ▂▇▆▇▇
3 number_project                0             1   3.81    1.24   2      3      4      5        7 ▇▅▃▂▁
4 average_monthly_hours         0             1 201.     50.0   96    156    199    245      310 ▃▇▆▇▂
5 time_spend_company            0             1   3.52    1.49   2      3      3      4       10 ▇▃▁▁▁
6 Work_accident                 0             1   0.147   0.354  0      0      0      0        1 ▇▁▁▁▂
7 left                          0             1   0.236   0.425  0      0      0      0        1 ▇▁▁▁▂
8 promotion_last_5years         0             1   0.0208  0.143  0      0      0      0        1 ▇▁▁▁▁
#plot_num(Data)

# Let's look at this without our dummy coded data
Data %>% 
   select("satisfaction_level", 
          "last_evaluation", 
          "number_project",       
          "average_monthly_hours", 
          "time_spend_company") %>% 
  plot_num()
Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

*Our numeric variables are not very normally distributed. Average monthly and last evaluation appear bimodal.The remaining variables are somewhat skewed.

# DUMMY CODED
Data %>% 
   select("Work_accident", 
          "left", 
          "promotion_last_5years") %>% 
  plot_num()
Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

Our category variables are unbalanced.

  • There are very few promotions in the past 5 years.
  • There are relatively few observations having Work Accidents.
## Left: frequency distribution of all discrete variables
plot_bar(Data)


## Right: `left` distribution of all discrete variables
plot_bar(Data, with = "left")

  • There are no big differences in distribution when accounting for left. Some of the departments with less representation have more attrition, such as HR and RandD.
plot_bar(Data, by = "left")

plot_bar(Data, by = "salary")

Data %>% 
  select("satisfaction_level", 
         "last_evaluation", 
         "number_project",       
         "average_monthly_hours", 
         "time_spend_company") %>% 
  plot_histogram()
select: dropped 5 variables (Work_accident, left, promotion_last_5years, department, salary)

NA
Data %>% 
  select("average_monthly_hours", 
         "last_evaluation", 
         "satisfaction_level") %>% 
  plot_density()
select: dropped 7 variables (number_project, time_spend_company, Work_accident, left, promotion_last_5years, …)

#QQ PLOTS
Data %>% 
  select("average_monthly_hours", 
         "last_evaluation", 
         "satisfaction_level") %>% 
    plot_qq()
select: dropped 7 variables (number_project, time_spend_company, Work_accident, left, promotion_last_5years, …)

  • We see the general shape of residuals that we seek (s-curve).

Check for Variance

left <- Data$left #Create variable
Data %>% 
  plot_prcomp(maxcat = 5L)
1 features with more than 5 categories ignored!
department: 10 categories

var(left)
[1] 0.1805456

Data Summary

As we see in the Data:

Observations: 9,999 with Variables: 10

There is no missing data or duplicate data.

The Outcome variable is left with 7635 (76%) ‘No’ and 2364 (24%) ‘Yes’. This variable indicates if an employee left the company.

Three of our variables are dummy coded (0=No/1=Yes), including Work_accident, left, promotion_last_5years. One of these variables, Left, is our outcome variable, determining if someone stayed at or left the company.

Three of our numeric variables appear to be continuous, including average_monthly_hours, satisfaction_level, and last_evaluation.

Two numeric variables are discrete, including only 6 (number_project) and 8 (time_spend_company) values each.

Demographic data is limited. Variables such as department, salary level, and time_spend_company (tenure in years) are the closest thing to demographic information we have.

Some variables provide personal information, such as satisfaction_level and Work_accident. And a few indicate overall performance for the employee, such as last_evaluation and promotion_last_5years.

We’ll need to account for unbalance in the dataset in preprocessing. We’ll also need to normalize (center and scale) numeric predictors.

Data Exploration

Correlations

library(corrr)
Warning: package ‘corrr’ was built under R version 4.0.5
Data %>% select(where(is.numeric)) %>% 
  correlate()
select: dropped 2 variables (department, salary)

Correlation method: 'pearson'
Missing treated using: 'pairwise.complete.obs'
#CREATE OBJECT OF CORRELATION MATRIX  
cor_matrix <- Data %>% 
  select(where(is.numeric)) %>% 
  correlate()
select: dropped 2 variables (department, salary)

Correlation method: 'pearson'
Missing treated using: 'pairwise.complete.obs'
#REARRANGE SHOULD GROUP ANY HIGHLY CORRELATED COLUMNS
cor_matrix %>% 
   rearrange()
Registered S3 methods overwritten by 'registry':
  method               from 
  print.registry_field proxy
  print.registry_entry proxy
cor_matrix %>% 
  corrr::focus(left) %>% 
  arrange(desc(left))
cor_matrix %>% 
  corrr::focus(left, number_project, satisfaction_level) %>% 
  arrange(desc(left))
cor_matrix %>% 
  stretch(na.rm = TRUE, remove.dups = TRUE)

#To create a structured version of our correlation data, which we can pass to ggplot or dplyr for further data analysis, we can use the stretch() function. https://www.gmudatamining.com/lesson-07-r-tutorial.html
# histogram of the correlation values observed for all unique pairs of variables in cor_matrix

cor_matrix %>% 
  stretch(na.rm = TRUE, remove.dups = TRUE) %>% 
  ggplot(mapping = aes(x = r)) +
  geom_histogram(fill = '#006EA1', color = 'white', bins = 15) +
  labs(title = 'Correlation Values',
       x = 'Correlation Value',
       y = 'Count')

cor_matrix2 <- Data %>% 
                   select(where(is.numeric)) %>% 
                   cor()
select: dropped 2 variables (department, salary)
cor_matrix2
                      satisfaction_level last_evaluation number_project average_monthly_hours
satisfaction_level            1.00000000     0.110623371   -0.154398550          -0.029060284
last_evaluation               0.11062337     1.000000000    0.352631362           0.349228288
number_project               -0.15439855     0.352631362    1.000000000           0.416829145
average_monthly_hours        -0.02906028     0.349228288    0.416829145           1.000000000
time_spend_company           -0.10824181     0.133035838    0.195597643           0.127196959
Work_accident                 0.06665317    -0.013389981   -0.009984353          -0.014007727
left                         -0.38759296     0.002576301    0.029574973           0.068494570
promotion_last_5years         0.02169405    -0.014264824   -0.004259769          -0.003583158
                      time_spend_company Work_accident         left promotion_last_5years
satisfaction_level          -0.108241811   0.066653172 -0.387592956           0.021694046
last_evaluation              0.133035838  -0.013389981  0.002576301          -0.014264824
number_project               0.195597643  -0.009984353  0.029574973          -0.004259769
average_monthly_hours        0.127196959  -0.014007727  0.068494570          -0.003583158
time_spend_company           1.000000000   0.008205063  0.131600744           0.065150746
Work_accident                0.008205063   1.000000000 -0.160663519           0.046291356
left                         0.131600744  -0.160663519  1.000000000          -0.061312236
promotion_last_5years        0.065150746   0.046291356 -0.061312236           1.000000000
corrplot(cor_matrix2, 
         method = "ellipse", # ellipses instead of circles
         type = "upper", # keep upper half only
         order = "FPC", # order by loadings on FPC
         tl.col = "black") # color of variable text lables

Correlation Summary

There are no variables that are overly correlated that need to be removed. Collinearity should not be an issue. Relationships in variables seems sensical. For example - Number of Projects, Average Monthly Hours, and Last Evaluation all correlate more strongly with one another, which makes sense in a work environment. Lower correlations are seen between satisfaction level and “left”, which we would also expect.

Cross-Tabs

#CREATE A TABLE OF MEANS FOR NUMERIC VARIABLE, GROUPED BY OUTCOME VARIABLE
Data %>%
  group_by(left) %>%
  summarise(
          count = n(),
          mean_sat = mean(satisfaction_level),
          mean_eval = mean(last_evaluation),
          mean_proj = mean(number_project),
          mean_hrs = mean(average_monthly_hours),
          mean_yrs = mean(time_spend_company)
            )
group_by: one grouping variable (left)
summarise: now 2 rows and 7 columns, ungrouped

Grouping by our outcome variable, left, we can see that the mean for many of our predictor variables does not vary by much. The largest difference is found in satisfaction_level, with those who left showing a lower satisfaction level, on average.

#SATISFACTION BY SALARY LEVEL
Data %>%
group_by(salary) %>%
  summarise(
    n = n(),
    mean = mean(satisfaction_level),
    sd = sd(satisfaction_level)
  )
group_by: one grouping variable (salary)
summarise: now 3 rows and 4 columns, ungrouped

Average Satisfaction level does not vary much by salary level, although it does get slightly higher at each level.

#UNDERSTANDING COMBINATIONS OF DATA 
Data %>%
  tabyl(left, salary) %>% 
  adorn_totals(c('row', 'col')) %>%
  adorn_percentages('col') %>% 
  adorn_pct_formatting(digits = 0) %>%
  adorn_ns() %>%
  adorn_title('combined')
 left/salary         low      medium       high       Total
           0  71% (3453)  79% (3413)  92% (769)  76% (7635)
           1  29% (1413)  21%  (888)   8%  (63)  24% (2364)
       Total 100% (4866) 100% (4301) 100% (832) 100% (9999)
  • Departures by salary level follows the overall dataset balance of approximately 75% stay / 25% leave at Low and Medium levels.

  • Exception: For those with high salary, only 8% left the company.

Data %>%
  tabyl(left, department)  %>% 
  adorn_totals(c('row', 'col')) %>%
  adorn_percentages('col') %>% 
  adorn_pct_formatting(digits = 0) %>%
  adorn_ns() %>%
  adorn_title('combined')
 left/department accounting         hr         IT management  marketing product_mng      RandD       sales
               0  74% (382)  72% (355)  78% (637)  85% (360)  77% (459)   77% (454)  85% (448)  75% (2079)
               1  26% (134)  28% (138)  22% (179)  15%  (66)  23% (140)   23% (135)  15%  (76)  25%  (675)
           Total 100% (516) 100% (493) 100% (816) 100% (426) 100% (599)  100% (589) 100% (524) 100% (2754)
     support   technical       Total
  75% (1128)  75% (1333)  76% (7635)
  25%  (371)  25%  (450)  24% (2364)
 100% (1499) 100% (1783) 100% (9999)
  • We see a relatively even split of departures in each department, tending toward 75% stay / 25% go, which matches our overall dataset balance.

  • The only exception is Management (85% stay). (Also, our Random Variable has an 85% / 15% split)

Data %>%
  tabyl(left, number_project) %>% 
  adorn_totals(c('row', 'col')) %>%
  adorn_percentages('col') %>% 
  adorn_pct_formatting(digits = 0) %>%
  adorn_ns() %>%
  adorn_title('combined')
 left/number_project           2           3           4           5          6          7       Total
                   0  35%  (552)  98% (2695)  91% (2583)  78% (1452)  44% (353)   0%   (0)  76% (7635)
                   1  65% (1041)   2%   (46)   9%  (254)  22%  (401)  56% (443) 100% (179)  24% (2364)
               Total 100% (1593) 100% (2741) 100% (2837) 100% (1853) 100% (796) 100% (179) 100% (9999)
  • The relationship of the number_project variable to outcome variable varies from others. There seems to be a “sweet spot” of 3 or 4 projects having less attrition. Those with 7 projects attrited at 100%.
Data %>%
  tabyl(left, promotion_last_5years) %>% 
  adorn_totals(c('row', 'col')) %>%
  adorn_percentages('col') %>% 
  adorn_pct_formatting(digits = 0) %>%
  adorn_ns() %>%
  adorn_title('combined')
 left/promotion_last_5years           0          1       Total
                          0  76% (7439)  94% (196)  76% (7635)
                          1  24% (2352)   6%  (12)  24% (2364)
                      Total 100% (9791) 100% (208) 100% (9999)
  • For those who have not been promoted in last 5 years, our expected attrition rate holds. There is a much higher retention rate (94%) for those who have been promoted.
Data %>%
  tabyl(left, Work_accident) %>% 
  adorn_totals(c('row', 'col')) %>%
  adorn_percentages('col') %>% 
  adorn_pct_formatting(digits = 0) %>%
  adorn_ns() %>%
  adorn_title('combined')
 left/Work_accident           0           1       Total
                  0  74% (6270)  93% (1365)  76% (7635)
                  1  26% (2258)   7%  (106)  24% (2364)
              Total 100% (8528) 100% (1471) 100% (9999)
  • For those who have not had a work accident, our expected attrition rate holds. There is a much higher retention rate (93%) for those who have been in an accident.
Data %>%
  tabyl(left, time_spend_company) %>% 
  adorn_totals(c('row', 'col')) %>%
  adorn_percentages('col') %>% 
  adorn_pct_formatting(digits = 0) %>%
  adorn_ns() %>%
  adorn_title('combined')
 left/time_spend_company         10           2           3           4          5          6          7
                       0 100% (156)  98% (2122)  75% (3205)  66% (1118)  44% (441)  72% (350) 100% (119)
                       1   0%   (0)   2%   (36)  25% (1058)  34%  (584)  56% (551)  28% (135)   0%   (0)
                   Total 100% (156) 100% (2158) 100% (4263) 100% (1702) 100% (992) 100% (485) 100% (119)
          8       Total
 100% (124)  76% (7635)
   0%   (0)  24% (2364)
 100% (124) 100% (9999)
  • There is 100% retention for employees who have stayed with the company for 7 or more years.

Data %>%
group_by(left) %>%
  summarise(
    n = n(),
    mean = mean(satisfaction_level),
    sd = sd(satisfaction_level)
  )
group_by: one grouping variable (left)
summarise: now 2 rows and 4 columns, ungrouped
  • Average Satisfaction level is much lower for people who left the company.

Data %>%
group_by(left) %>%
  summarise(
    n = n(),
    mean = mean(last_evaluation),
    sd = sd(last_evaluation)
  )
group_by: one grouping variable (left)
summarise: now 2 rows and 4 columns, ungrouped
  • Average score on the last evaluation is approximately even for those who left the company.

Data %>%
group_by(left) %>%
  summarise(
    n = n(),
    mean = mean(average_monthly_hours),
    sd = sd(average_monthly_hours)
  )
group_by: one grouping variable (left)
summarise: now 2 rows and 4 columns, ungrouped
  • Average monthly hours is slightly higher for those who left the company.

Crosstab Visualizations

After examining the crosstab tables of various combinations of our data, we’ll also run some visualizations to see if anything else may pop out that is surprising or interesting for our modelling purposes.


#library(CGPfunctions) 
#https://www.youtube.com/watch?v=BoAvAuOYv3s

#VISUALIZING

PlotXTabs2(Data, department, time_spend_company, results.subtitle = FALSE)

PlotXTabs2(Data, department, number_project, results.subtitle = FALSE)

PlotXTabs2(Data, department, time_spend_company, results.subtitle = FALSE)


PlotXTabs(Data, department, salary)
Plotted dataset Data variables department by salary

PlotXTabs(Data, number_project, salary)
Plotted dataset Data variables number_project by salary

PlotXTabs(Data, left, department)
Plotted dataset Data variables left by department

PlotXTabs(Data, left, salary)
Plotted dataset Data variables left by salary

PlotXTabs(Data, left, number_project)
Plotted dataset Data variables left by number_project

PlotXTabs(Data, left, time_spend_company)
Plotted dataset Data variables left by time_spend_company

Crosstab Observations Summary

In viewing various combinations of our variables, the relationships seem to make sense for the data set explored. For example, average satisfaction level decreases for those who left the company and increases slightly for each level of salary. Average monthly hours is slightly higher for people who left the company.

On the whole, our dataset has an approximately 75/25 split of retention and attrition. In most cases, this attrition percentage split stayed constant when accounting for various facets of the data. A few exceptions to the ~25% attrition rate were noted:

  • When salary is High = 8% attrition
  • When department is Management = 15% attrition
  • When number_projects is… – 7 projects = 100% attrition (high) – 6 projects = 56% attrition (high) – 5 projects = 22% attrition (expected) – 4 projects = 9% attrition (low) – 3 projects = 2% attrition (low) – 2 projects = 65% attrition (high)
  • When promoted_last_5yrs is Yes = 6% attrition
  • When Work_accident is Yes = 7% attrition
  • When time_spend_company is >7 years = 0% attrition

Splitting Data for Modeling

Outcome Class Imbalance

# ATTRITION PERCENTAGE IN DATASET
Data %>%
  tabyl(left) %>% 
  adorn_pct_formatting(digits = 0, affix_sign = TRUE)
 left    n percent
    0 7635     76%
    1 2364     24%

Taking a look at our outcome variable we find that Our data is unbalanced, given about 1/4 of the observations left the organization and 3/4’s stayed. We’ll need to account for this in our recipes.

Create Training & Test Data

First, We’ll make our outcome variable a factor for easier working outside of recipes.

Data <- Data %>%
  mutate(left = as.factor(left))
mutate: converted 'left' from double to factor (0 new NA)

Then, we’ll set seed and split the data. We’re using a default 75/25 split, as we have a nice large amount of data and this should leave enough data in each set.

set.seed(1013)

#SPLIT DATA WITH CONSIDERATION OF OUTCOME VARIABLE `LEFT`
data_split <- initial_split(Data, prop = 0.75, strata = "left")

#CREATE TRAINING & TEST DATA SETS
train_data <- training(data_split)
test_data <- testing(data_split)

#DOUBLE CHECK OUTCOME SPLIT IN TRAINING & TEST DATA SETS
tabyl(train_data$left)
 train_data$left    n   percent
               0 5726 0.7635685
               1 1773 0.2364315
tabyl(test_data$left)
 test_data$left    n percent
              0 1909  0.7636
              1  591  0.2364

Training data and test data sets were created with a 75 / 25 split of original observations. Due to imbalance, we specified our outcome variable (left) when splitting to ensure training and test sets had an approximately equal number of retention & attrition in the outome variable. Checking the resulting sets shows us we successfully held the 76%/24% ratio of left No/Yes observations.

This imbalance in Training set will be accounted for in model pre-processing

Cross Validation V-Folds creation

Now to go ahead and create our splits to use in modeling later.

set.seed(1013)
cv_folds <- vfold_cv(train_data, strata = left, v = 10) 

map_dbl(cv_folds$splits,
        function(x) {
          dat <- as.data.frame(x)$left
          mean(dat == "Yes")
        })
 [1] 0 0 0 0 0 0 0 0 0 0

Models

To achieve our final goal of finding a predictive model for attrition that can be used to quantify the ROI of an intervention, we’ll try several models. Our goal is both the most predictive model, but also one that is interpretable. We’ll start with a more complex model to see how predictive it is and then run a simpler model to compare effectiveness.

Model & Recipe Prep

First we’ll prep several model specifications.


#CREATING MODELS FOR SCREENING
#Helpful model choice approach chart: https://scikit-learn.org/stable/tutorial/machine_learning_map/index.html

#Logistic Regression
#SIMPLEST 
spec_logistic <- 
  logistic_reg(penalty = tune(), mixture = tune()) %>% 
  set_engine("glm") %>% 
  set_mode("classification")

#Random Forest - Decision Tree Method
#MORE COMPLEX
spec_rf <- 
  rand_forest(mtry = 5, trees = 10000, min_n = 4) %>%  #Replace with trees = #tune()
  set_engine("randomForest", importance = TRUE) %>%  #set_engine("ranger", importance = "impurity)
  set_mode("classification") %>% 
  parsnip::translate()

#Neural Net
#MORE COMPLEX
spec_nnet <- 
   mlp() %>% 
   set_engine("keras", verbose = 0) %>% 
   set_mode("classification")

#Support Vector Machine (LinearSV Classification)
#MORE COMPLEX
spec_svm_linear2 <-
  svm_linear(cost = 1) %>%
  set_engine("kernlab") %>%
  set_mode("classification")


#SOME OTHER MODEL IDEAS
# dt_loan <- decision_tree(cost_complexity = tune(), tree_depth = tune(), min_n = tune()) %>% 
#   set_engine("rpart") %>% 
#   set_mode("classification")
# 
# xgboost_loan <- boost_tree(mtry = tune(), trees = tune(), min_n = tune(), tree_depth = tune(), learn_rate = tune(), loss_reduction = tune(), sample_size = tune())  %>% 
#   set_engine("xgboost") %>% 
#   set_mode("classification")
# 
# #K-Nearest Neighbors - This is a type of SVM
# knn_loan <- nearest_neighbor(neighbors = tune(), weight_func = tune(), dist_power = tune()) %>% 
#   set_engine("kknn") %>% 
#   set_mode("classification")

# spec_rf <- 
#   rand_forest(mtry = tune(), trees = 1000, min_n = tune()) %>%  #Replace with trees = #tune()
#   set_engine("randomForest", importance = TRUE) %>% 
#   set_mode("classification")

# spec_svm_linear <- 
#   svm_poly(degree = 1) %>% 
#   set_engine("kernlab") %>% 
#   set_mode("classification")

Then we’ll create recipes that can handle preprocessing of our data. Given the imbalance in the outcome variable, we’ll add steps to correct in our training data. We’ll also center and scale (normalize) data since we saw there was a good bit of non-normalness and bifurcated categorizations in predictor variables.

#CREATE FOUR RECIPES

#"Some models (notably neural networks, K-nearest neighbors, and support vector machines) require predictors that have been centered and scaled" - https://www.tmwr.org/workflow-sets.html

#PREPROCESSING RULES FROM KUHN, p550
#LOGISTIC REGRESSION - CS, NZV, REMOVE CORR VARS
#NEURAL NET - CS, NZV, REMOVE CORR VARS
#SVM - CS
#RANDOM FOREST - N/A

set.seed(1013)

conflict_prefer("step_upsample", "themis")
[conflicted] Removing existing preference
[conflicted] Will prefer themis::step_upsample over any other package
#RECIPE 1 - Centered & Scaled-BoxCox; Upsample; NZV
recipe_1 <- recipe(left ~., data = train_data) %>% 
  #update_role(ID, new_role = "id") %>% 
  step_BoxCox(all_numeric_predictors())%>%
  step_normalize(all_numeric_predictors()) %>%
  step_dummy(all_nominal_predictors()) %>% 
  step_nzv(all_predictors()) %>%
  step_upsample(left, skip = TRUE)  
  
#RECIPE 2 - Centered & Scaled; Upsample; NZV
recipe_2 <- recipe(left ~., data = train_data) %>% 
  #update_role(ID, new_role = "id") %>%
  step_center(all_numeric_predictors()) %>% 
  step_scale(all_numeric_predictors()) %>%
  step_dummy(all_nominal_predictors()) %>% 
  step_nzv(all_predictors()) %>% 
  step_upsample(left, skip = TRUE)

#RECIPE 3 - Not Centered & Scaled; Upsample
recipe_3 <- recipe(left ~., data = train_data) %>% 
 # update_role(ID, new_role = "id") %>%      ## Per Julia Silge
  step_dummy(all_nominal_predictors()) %>% 
  step_upsample(left, skip = TRUE)

#RECIPE 4 - Not Centered & Scaled; Upsample; NZV
recipe_4 <- recipe(left ~., data = train_data) %>% 
  #update_role(ID, new_role = "id")  %>%
  step_dummy(all_nominal_predictors()) %>% 
  step_nzv(all_predictors()) %>% 
  step_upsample(left, skip = TRUE)
  

1 Support Vector Classifier

We’ll start by running a complicated model to set “ceiling potential” for prediction accuracy.

svm_workflow <- workflow() %>% 
  add_recipe(recipe_2) %>% 
  add_model(spec_svm_linear2)
  
svm_workflow
== Workflow ===============================================================================================
Preprocessor: Recipe
Model: svm_linear()

-- Preprocessor -------------------------------------------------------------------------------------------
5 Recipe Steps

* step_center()
* step_scale()
* step_dummy()
* step_nzv()
* step_upsample()

-- Model --------------------------------------------------------------------------------------------------
Linear Support Vector Machine Specification (classification)

Main Arguments:
  cost = 1

Computational engine: kernlab 
#RESIDUALS WITHOUT TUNING
conflict_prefer("alpha", "scales")
[conflicted] Will prefer scales::alpha over any other package
svm_res <- 
  svm_workflow %>% 
  fit_resamples(
    resamples = cv_folds, 
    metrics = metric_set(
      recall, precision, f_meas, 
      accuracy, kap,
      roc_auc, sens, spec),
    control = control_resamples(
      save_pred = TRUE)
    ) 
Warning: package ‘rlang’ was built under R version 4.0.5
Warning: package ‘vctrs’ was built under R version 4.0.5
Warning: package ‘kernlab’ was built under R version 4.0.3
svm_res
# Resampling results
# 10-fold cross-validation using stratification 
#FIT THE MODEL WITH TRAINING DATA
svm_fit <- svm_workflow %>% 
  fit(data = train_data)
 Setting default kernel parameters  
svm_fit
== Workflow [trained] =====================================================================================
Preprocessor: Recipe
Model: svm_linear()

-- Preprocessor -------------------------------------------------------------------------------------------
5 Recipe Steps

* step_center()
* step_scale()
* step_dummy()
* step_nzv()
* step_upsample()

-- Model --------------------------------------------------------------------------------------------------
Support Vector Machine object of class "ksvm" 

SV type: C-svc  (classification) 
 parameter : cost C = 1 

Linear (vanilla) kernel function. 

Number of Support Vectors : 6539 

Objective Function Value : -6516.875 
Training error : 0.21219 
Probability model included. 
  
  • Without any input, the model has an error rate of .21, which is a little better than chance (.24) but not much.
#PLOT TUNED RESIDUALS
tune_res <- tune_grid(
  svm_tune_wf, 
  resamples = sim_data_fold, 
  grid = param_grid
)
x Fold04: preprocessor 1/1, model 5/10 (predictions): Error: $ operator is invalid for atomic vectors
x Fold09: preprocessor 1/1, model 4/10 (predictions): Error: $ operator is invalid for atomic vectors
autoplot(tune_res)

#Plugging in best value for cost for final model fit
best_cost_svm <- select_best(tune_res, metric = "roc_auc")

svm_linear_final <- finalize_workflow(svm_workflow, best_cost_svm)

svm_linear_fit <- svm_linear_final %>% fit(train_data)
 Setting default kernel parameters  
svm_linear_fit
== Workflow [trained] =====================================================================================
Preprocessor: Recipe
Model: svm_linear()

-- Preprocessor -------------------------------------------------------------------------------------------
5 Recipe Steps

* step_center()
* step_scale()
* step_dummy()
* step_nzv()
* step_upsample()

-- Model --------------------------------------------------------------------------------------------------
Support Vector Machine object of class "ksvm" 

SV type: C-svc  (classification) 
 parameter : cost C = 1 

Linear (vanilla) kernel function. 

Number of Support Vectors : 6539 

Objective Function Value : -6516.875 
Training error : 0.21219 
Probability model included. 
#CONFUSION MATRIX WITH TUNED COST
augment(svm_linear_fit, new_data = test_data) %>%
  conf_mat(truth = left, estimate = .pred_class)
          Truth
Prediction    0    1
         0 1349   83
         1  560  508
#           Truth
# Prediction    0    1
#          0 1791  434
#          1  118  157

TO DO - UPDATE THIS DESCRIPTION AFTER RERUNNING * 53.9% (n=1349) Prediction was No Attrition, Truth was No Attrition * 6.3% (n=508) Prediction was Attrition, Truth was Attrition

77.9% Accurate prediction

  • 4.7% (n=560) Prediction was Attrition, Truth was No Attrition
  • 17.4% (n=83) Prediction was No Attrition, Truth was Attrition

22.1% Inaccurate prediction

In an ROI scenario - this model would

augment(svm_linear_fit, new_data = test_data) %>%
  conf_mat(truth = left, estimate = .pred_class) %>% 
  summary()

2 Logistic Regression

After running a more complicated SVM model to see how good it could be, we’ll run a simple Logistic Regression to compare and contrast.

#LOGISTIC REGRESSION WORKFLOW
logit_wflow <- workflow() %>%
   add_recipe(recipe_2) %>%
   add_model(spec_logistic)

logit_wflow
== Workflow ===============================================================================================
Preprocessor: Recipe
Model: logistic_reg()

-- Preprocessor -------------------------------------------------------------------------------------------
5 Recipe Steps

* step_center()
* step_scale()
* step_dummy()
* step_nzv()
* step_upsample()

-- Model --------------------------------------------------------------------------------------------------
Logistic Regression Model Specification (classification)

Main Arguments:
  penalty = tune()
  mixture = tune()

Computational engine: glm 
#RESIDUALS WITHOUT TUNING
log_res <- 
  logit_wflow %>% 
  fit_resamples(
    resamples = cv_folds, 
    metrics = metric_set(
      recall, precision, f_meas, 
      accuracy, kap,
      roc_auc, sens, spec),
    control = control_resamples(
      save_pred = TRUE)
    ) 

log_res
# Resampling results
# 10-fold cross-validation using stratification 
# save model coefficients for a fitted model object from a workflow

get_model <- function(x) {
  pull_workflow_fit(x) %>% tidy()
}

# same as before with one exception
log_res_2 <- 
  logit_wflow %>% 
  fit_resamples(
    resamples = cv_folds, 
    metrics = metric_set(
      recall, precision, f_meas, 
      accuracy, kap,
      roc_auc, sens, spec),
    control = control_resamples(
      save_pred = TRUE,
      extract = get_model) # use extract and our new function
    ) 
! Fold01: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
! Fold02: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
! Fold03: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
! Fold04: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
! Fold05: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
! Fold06: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
! Fold07: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
! Fold08: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
! Fold09: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
! Fold10: internal: `pull_workflow_fit()` was deprecated in workflows 0.2.3.
Please use `extract_fit_parsnip(...
all_coef <- map_dfr(log_res_2$.extracts, ~ .x[[1]][[1]]) #Results flattened and collected

log_res_2$.extracts[[1]][[1]] #Show results
[[1]]
NA
  • Satisfaction Level, Time at Company, Work Accident, Number of Projects, High Salary are the top predictor variables. Most of these won’t be addressable by an intervention. We may need to do some feature engineering to make the model more relevant to our use case.
set.seed(1013)

#Fit with formula and model
#CROSS VALIDATE LOGIT_WKFLOW ON CV FOLDS

fit_resamples(
  logit_wflow,
  model = spec_logistic,          
  resamples = cv_folds
)
Warning: The `...` are not used in this function but one or more objects were passed: 'model'
# Resampling results
# 10-fold cross-validation using stratification 

#PRINT RESULTS
#sAVE AS Object

set.seed(1013) 

logit_tune_results <- fit_resamples(logit_wflow, 
              spec_logistic, 
              resamples = cv_folds) %>%
  collect_metrics()
Warning: The `...` are not used in this function but one or more objects were passed: ''
logit_last_fit <- logit_wflow %>%
  # fit on the training set and evaluate on the test set
  last_fit(data_split)

logit_last_fit
# Resampling results
# Manual resampling 
logit_test_performance <- logit_last_fit %>% collect_metrics()
logit_test_performance

Overall the performance is not great. Similar to our training data, accuracy of .74 and AUC of .81. Accuracy is even with our baseline of 74% and ROC is higher by 6%. This may be valuable for our purposes, depending on where the accuracy is coming from.

#Generate test predictions from the test set.

logit_test_predictions <- logit_last_fit %>% collect_predictions
logit_test_predictions
#Plot the ROC Curve
logit_test_predictions %>%
  roc_curve(left, .pred_0) %>% #Originally, was "pred_Yes" and curve was inverted
  ggplot(aes(x = 1 - specificity, y = sensitivity)) +
  geom_line(size = 1.5, color = "midnightblue") +
  geom_abline(
    lty = 2, alpha = 0.5,
    color = "gray50",
    size = 1.2
  ) 

# generate a confusion matrix
conflict_prefer("spec", "yardstick")
[conflicted] Removing existing preference
[conflicted] Will prefer yardstick::spec over any other package
logit_test_predictions %>%
  conf_mat(truth = left, estimate = .pred_class)
          Truth
Prediction    0    1
         0 1389  124
         1  520  467
#           Truth
# Prediction    0    1
#          0 1389  124
#          1  520  467

RESULTS

  • 55.5% (n=1389) Prediction was No Attrition, Truth was No Attrition

  • 18.7% (n=467) Prediction was Attrition, Truth was Attrition

74.2% Accurate prediction

  • 20.8% (n=520) Prediction was Attrition, Truth was No Attrition

  • 5.0% (n=124) Prediction was No Attrition, Truth was Attrition

25.8% Inaccurate prediction

This is slightly worse than our more complicated SVM model in overall prediction accuracy, but not by much. This model has a much smaller percentage of False Negatives than the SVM model, which is more useful when predicting attrition, given we will want to be sure our proposed intervention ROI is not overestimated.

More Logistic Regression Analysis

logit_test_predictions %>% 
  ggplot() +
  geom_density(aes(x = .pred_1, 
                   fill = left), 
               alpha = 0.5)

logit_test_predictions %>%
  conf_mat(truth = left, estimate = .pred_class) %>%
  summary()
  • Specficity = .79 (If we say someone is going to leave, we get it right with this model 79%.)
#LIST IMPORTANT VARIABLES
logit_final_model <- fit(logit_wflow, Data)

logit_final_model
== Workflow [trained] =====================================================================================
Preprocessor: Recipe
Model: logistic_reg()

-- Preprocessor -------------------------------------------------------------------------------------------
5 Recipe Steps

* step_center()
* step_scale()
* step_dummy()
* step_nzv()
* step_upsample()

-- Model --------------------------------------------------------------------------------------------------

Call:  stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)

Coefficients:
           (Intercept)      satisfaction_level         last_evaluation          number_project  
              -0.14259                -1.10614                 0.21005                -0.48835  
 average_monthly_hours      time_spend_company           Work_accident           department_IT  
               0.21479                 0.58968                -0.56055                -0.23031  
  department_marketing  department_product_mng        department_RandD        department_sales  
              -0.02386                 0.02850                -0.60682                -0.01250  
    department_support    department_technical           salary_medium             salary_high  
               0.06263                 0.23822                -0.43203                -1.72928  

Degrees of Freedom: 15269 Total (i.e. Null);  15254 Residual
Null Deviance:      21170 
Residual Deviance: 16040    AIC: 16070

3 Random Forest

rf_workflow <- workflow() %>% 
  add_recipe(recipe_3) %>% 
  add_model(spec_rf)
  
rf_workflow
== Workflow ===============================================================================================
Preprocessor: Recipe
Model: rand_forest()

-- Preprocessor -------------------------------------------------------------------------------------------
2 Recipe Steps

* step_dummy()
* step_upsample()

-- Model --------------------------------------------------------------------------------------------------
Random Forest Model Specification (classification)

Main Arguments:
  mtry = 5
  trees = 10000
  min_n = 4

Engine-Specific Arguments:
  importance = TRUE

Computational engine: randomForest 

Model fit template:
randomForest::randomForest(x = missing_arg(), y = missing_arg(), 
    mtry = min_cols(~5, x), ntree = 10000, nodesize = min_rows(~4, 
        x), importance = TRUE)

set.seed(1013)

rf_fit <- fit(rf_workflow, data = train_data)

rf_fit
== Workflow [trained] =====================================================================================
Preprocessor: Recipe
Model: rand_forest()

-- Preprocessor -------------------------------------------------------------------------------------------
2 Recipe Steps

* step_dummy()
* step_upsample()

-- Model --------------------------------------------------------------------------------------------------

Call:
 randomForest(x = maybe_data_frame(x), y = y, ntree = ~10000,      mtry = min_cols(~5, x), nodesize = min_rows(~4, x), importance = ~TRUE) 
               Type of random forest: classification
                     Number of trees: 10000
No. of variables tried at each split: 5

        OOB estimate of  error rate: 0.46%
Confusion matrix:
     0    1 class.error
0 5713   13 0.002270346
1   40 5686 0.006985679
#USING RANGER ENGINE, 1000 TREES. SAME RESULT WITH 10k TREES AND RANGER ENGINE
#WITH 1000 TREES
# Warning: tune columns were requested but there were 16 predictors in the data. 16 will be used.
# Warning: tune samples were requested but there were 11452 rows in the data. 11452 will be used.

# Call:
#  randomForest(x = maybe_data_frame(x), y = y, ntree = ~1000, mtry = min_cols(~tune(),      x), nodesize = min_rows(~tune(), x), importance = ~TRUE) 
#                Type of random forest: classification
#                      Number of trees: 1000
# No. of variables tried at each split: 16
# 
#         OOB estimate of  error rate: 21.73%
# Confusion matrix:
#      0    1 class.error
# 0 4899  827   0.1444289
# 1 1662 4064   0.2902550

#WITH 10000 TREES

# Ranger result
# 
# Call:
#  ranger::ranger(x = maybe_data_frame(x), y = y, mtry = min_cols(~tune(),      x), num.trees = ~10000, min.node.size = min_rows(~tune(),      x), importance = ~"impurity", num.threads = 1, verbose = FALSE,      seed = sample.int(10^5, 1), probability = TRUE) 
# 
# Type:                             Probability estimation 
# Number of trees:                  10000 
# Sample size:                      11452 
# Number of independent variables:  18 
# Mtry:                             18 
# Target node size:                 11452 
# Variable importance mode:         impurity 
# Splitrule:                        gini 
# OOB prediction error (Brier s.):  0.2500432 
  

Pulling the confusion matrix out of the output:

OOB estimate of error rate: 0.46%

Confusion matrix: 0 1 class.error 0 5713 13 0.002270346 1 40 5686 0.006985679

augment(rf_fit, new_data = test_data) %>%
  rmse(truth = as.numeric(left), estimate = as.numeric(.pred_class))

rf.roc<-roc(train_data$left, rf_final$votes[,2])
Setting levels: control = 0, case = 1
Setting direction: controls < cases
plot(rf.roc)

pROC::auc(rf.roc)
Area under the curve: 0.9889
rf_testing_pred %>% # test set predictions
  roc_auc(truth = left, .pred_1)

rf_testing_pred %>%                   # test set predictions
  accuracy(truth = left, .pred_class)
last_fit_rf <- last_fit(rf_workflow, 
                        split = data_split,
                        metrics = metric_set(
                          recall, precision, f_meas, 
                          accuracy, kap,
                          roc_auc, sens, spec)
                        )
Warning: package ‘parsnip’ was built under R version 4.0.5
Warning: package ‘tibble’ was built under R version 4.0.5
Warning: package ‘dials’ was built under R version 4.0.5
Warning: package ‘rsample’ was built under R version 4.0.5
Warning: package ‘workflows’ was built under R version 4.0.5
Warning: package ‘tidyr’ was built under R version 4.0.5
Warning: package ‘rlang’ was built under R version 4.0.5
Warning: package ‘vctrs’ was built under R version 4.0.5
Warning: package ‘randomForest’ was built under R version 4.0.5
#Show performance metrics

last_fit_rf %>% 
  collect_metrics()
rf_final

Call:
 randomForest(formula = left ~ ., data = train_data, mtry = 5,      trees = 10000, min_n = 4, keep.forest = TRUE, importance = TRUE) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 5

        OOB estimate of  error rate: 1.19%
Confusion matrix:
     0    1 class.error
0 5717    9 0.001571778
1   80 1693 0.045121263
importance(rf_final, sort = TRUE)
                               0          1 MeanDecreaseAccuracy MeanDecreaseGini
satisfaction_level    101.400344 337.898530           319.888350      1124.759934
last_evaluation        18.640044 111.035368           113.710098       302.200133
number_project         69.694218 239.160597           235.333583       445.560219
average_monthly_hours  62.586243 101.416338           111.910062       328.637547
time_spend_company     57.939171  82.418274            91.668575       459.621633
Work_accident           4.319829   8.684521             9.092943         4.591932
promotion_last_5years   2.830797   5.160545             5.064632         1.076596
department              7.580276  48.293374            29.060656        29.775646
salary                  7.210778  27.537577            19.853285        13.094145

Number of projects and Satisfaction level are strong predictors in this model.

varUsed(rf_final, count = TRUE, by.tree = FALSE)

4 Neural Net

#LOGISTIC REGRESSION WORKFLOW
nn_workflow <- workflow() %>%
  add_recipe(recipe_1) %>% 
  add_model(spec_nnet)

nn_workflow
set.seed(1013)

nnet_res <- 
  nn_workflow %>% 
  fit_resamples(
    resamples = cv_folds, 
    metrics = metric_set(
      recall, precision, f_meas, 
      accuracy, kap,
      roc_auc, sens, spec),
    control = control_resamples(save_pred = TRUE)
    ) 

nnet_res
library(NeuralNetTools)
#https://fawda123.github.io/NeuralNetTools/articles/Overview.html

nn_model <- nnet::nnet(left ~ ., data = train_data, size = 10)

par(mar = numeric(4))
plotnet(nn_model)

# The plotnet function plots a neural network as a simple network or as a neural interpretation diagram (NID). The default settings are to plot as NID with positive weights between layers as black lines and negative weights as grey lines. Line thickness is in proportion to relative magnitude of each weight. The first layer includes only input variables with nodes labelled as I1 through In for n input variables. One through many hidden layers are plotted with each node in each layer labelled as H1 through Hn. The output layer is plotted last with nodes labeled as O1 through On. Bias nodes connected to the hidden and output layers are also shown.
garson(nn_model)
olden(nn_model)

# The garson function uses Garson’s algorithm to evaluate relative variable importance. This function identifies the relative importance of explanatory variables for a single response variable by deconstructing the model weights. The importance of each variable can be determined by identifying all weighted connections between the layers in the network. That is, all weights connecting the specific input node that pass through the hidden layer to the response variable are identified. This is repeated for all other explanatory variables until a list of all weights that are specific to each input variable is obtained. The connections are tallied for each input node and scaled relative to all other inputs. A single value is obtained for each explanatory variable that describes the relationship with the response variable in the model. The results indicate relative importance as the absolute magnitude from zero to one. The function cannot be used to evaluate the direction of the response. Only neural networks with one hidden layer and one output node can be evaluated.

# The olden function is an alternative and more flexible approach to evaluate variable importance. The function calculates iportance as the product of the raw input-hidden and hidden-output connection weights between each input and output neuron and sums the product across all hidden neurons. An advantage of this approach is the relative contributions of each connection weight are maintained in terms of both magnitude and sign as compared to Garson’s algorithm which only considers the absolute magnitude. For example, connection weights that change sign (e.g., positive to negative) between the input-hidden to hidden-output layers would have a cancelling effect whereas Garson’s algorithm may provide misleading results based on the absolute magnitude. An additional advantage is that Olden’s algorithm is capable of evaluating neural networks with multiple hidden layers and response variables. The importance values assigned to each variable are in units that are based directly on the summed product of the connection weights. The actual values should only be interpreted based on relative sign and magnitude between explanatory variables. Comparisons between different models should not be made.
last_fit_nn <- last_fit(nn_workflow, 
                        split = data_split,
                        metrics = metric_set(
                          recall, precision, f_meas, 
                          accuracy, kap,
                          roc_auc, sens, spec)
                        )
#Show performance metrics

last_fit_nn %>% 
  collect_metrics()

These are our final performance metrics. The model performs well both on the training and test data. We have some insight into the variables driving our model, including Average Monthly Hours and Number of projects.

last_fit_nn %>% 
  collect_predictions() %>% 
  roc_curve(left, .pred_0) %>% 
  autoplot()

last_fit_nn %>% 
  collect_predictions() %>% 
  conf_mat(truth = left, estimate = .pred_class)

RESULTs: * 71.6% (n=1791) Prediction was No Attrition, Truth was No Attrition * 21.8% (n=545) Prediction was Attrition, Truth was Attrition – 93.4% Accurate prediction

  • 4.7% (n=118) Prediction was Attrition, Truth was No Attrition
  • 1.8% (n=46) Prediction was No Attrition, Truth was Attrition

    6.5% Inaccurate prediction

This is a much better accuracy than either the SVM Or Logistic Regression models in all directions. However, interpretability may be too difficult to make it useful.

Compare Models

#https://www.kirenz.com/post/2021-02-17-r-classification-tidymodels/#last-evaluation-on-test-set

log_metrics <- 
  log_res %>% 
  collect_metrics(summarise = TRUE) %>%
  mutate(model = "Logistic Regression") 

rf_metrics <-
  rf_res %>%
  collect_metrics(summarise = TRUE) %>%
  mutate(model = "Random Forest") #Seem to be running into a Windows error / known bug in package preventing ability to get these metrics for Random Forest model

svm_metrics <- 
  svm_res %>% 
  collect_metrics(summarise = TRUE) %>%
  mutate(model = "Support Vector Classifier")

nnet_metrics <-
  nnet_res %>%
  collect_metrics(summarise = TRUE) %>%
  mutate(model = "Neural Net")

# create dataframe with all models
model_compare <- bind_rows(
                          log_metrics,
                           rf_metrics,
                           svm_metrics,
                           nnet_metrics
                           ) 

# change data structure
model_comp <- 
  model_compare %>% 
  select(model, .metric, mean, std_err) %>% 
  pivot_wider(names_from = .metric, values_from = c(mean, std_err)) 
# show mean Accuracy-Score for every model
model_comp %>% 
  arrange(mean_accuracy) %>% 
  mutate(model = forcats::fct_reorder(model, mean_accuracy)) %>% # order results
  ggplot(aes(model, mean_accuracy, fill=model)) +
  geom_col() +
  coord_flip() +
  scale_fill_brewer(palette = "Blues") +
   geom_text(
     size = 3,
     aes(label = round(mean_f_meas, 2), y = mean_f_meas + 0.08),
     vjust = 1
  )
# show mean area under the curve (auc) per model
model_comp %>% 
  arrange(mean_roc_auc) %>% 
  mutate(model = forcats::fct_reorder(model, mean_roc_auc)) %>%
  ggplot(aes(model, mean_roc_auc, fill=model)) +
  geom_col() +
  coord_flip() +
  scale_fill_brewer(palette = "Blues") + 
     geom_text(
     size = 3,
     aes(label = round(mean_roc_auc, 2), y = mean_roc_auc + 0.08),
     vjust = 1
  )

Table 1

Feature Coefficients

APPENDIX - COMMENTED OUT NOTES & THOUGHTS FOR FUTURE PROJECTS

Business Value: Visualizing Model Performance

Turns out there is a bug in this package so we can’t make all the fancy charts. wah wah


#FROM: https://www.kdnuggets.com/2019/06/modelplotr-cran-business-value-predictive-models.html


# set.seed(1013)
# 
# #LOGIT
# logit_final <- logistic_reg(left ~ ., mode = "classification", engine = "glm")
# 
# logit_final_fit <- 
#   logit_final %>% 
#   fit(left ~ ., data = train_data)
# 
# #RF
# rf_final <- randomForest(left ~ ., data = train_data, mtry = 5, trees = 10000, min_n = 4, keep.forest = FALSE, importance = TRUE) 
# 
# # rf_final_fit <- 
# #   rf_final %>% 
# #   fit(left ~ ., data = train_data)
# 
# #NN
# nn_final <- mlp(left ~ ., mode = "classification", engine = "keras")
# 
# # nn_final_fit <- 
# #   nn_final %>% 
# #   fit(left ~ ., data = train_data)
# 
# #SVM
# svm_final <- svm_linear(left ~ ., mode = "classification", engine = "kernlab", cost = 1)
# 
# svm_final_fit <- 
#   svm_final %>% 
#   fit(left ~ ., data = train_data)
#library(modelplotr)

# transform datasets and model objects into scored data and calculate ntiles 
# preparation steps
# scores_and_ntiles <- prepare_scores_and_ntiles(datasets = list("train_data","test_data"),
#                       dataset_labels = list("train data","test data"),
#                       models = list("logit_final_fit","rf_final_fit", "nn_final", "svm_final_fit"), 
#                       model_labels = list("Logistic Regression","Random Forest", "Neural Net",
#                                           "Support Vector Classifier"),  
#                       target_column="left",
#                       ntiles=100)

#Error: Did you mean to use `new_data` instead of `newdata`? -- insurmountable bug
# transform data generated with prepare_scores_and_ntiles into aggregated data for chosen plotting scope 

# plot_input_log <- plotting_scope(prepared_input = scores_and_ntiles,
#                              select_model_label = "Logistic Regression",
#                              select_dataset_label = "test data")
# 
# plot_input_rf <- plotting_scope(prepared_input = scores_and_ntiles,
#                              select_model_label = "Random Forest",
#                              select_dataset_label = "test data")
# 
# plot_input_nn <- plotting_scope(prepared_input = scores_and_ntiles,
#                              select_model_label = "Neural Net",
#                              select_dataset_label = "test data")
# 
# plot_input_svm <- plotting_scope(prepared_input = scores_and_ntiles,
#                              select_model_label = "Support Vector Classifier",
#                              select_dataset_label = "test data")
# plot all four evaluation plots and save to file, highlight decile 2
# plot_multiplot(data = plot_input_log, highlight_ntile=2,
#                save_fig = TRUE, save_fig_filename = 'Predictive Attrition Model')
# 
# plot_multiplot(data = plot_input_rf, highlight_ntile=2,
#                save_fig = TRUE, save_fig_filename = 'Predictive Attrition Model')
# 
# plot_multiplot(data = plot_input_nn, highlight_ntile=2,
#                save_fig = TRUE, save_fig_filename = 'Predictive Attrition Model')
# 
# plot_multiplot(data = plot_input_svm, highlight_ntile=2,
#                save_fig = TRUE, save_fig_filename = 'Predictive Attrition Model')
#Profit plot - automatically highlighting the ntile where profit is maximized!
# plot_profit(data = plot_input_log, 
#             fixed_costs = 100000, 
#             variable_costs_per_unit = 5000,
#             profit_per_unit = 95000)
# 
# plot_profit(data = plot_input_rf, 
#             fixed_costs = 100000, 
#             variable_costs_per_unit = 5000,
#             profit_per_unit = 95000)
# 
# plot_profit(data = plot_input_nn, 
#             fixed_costs = 100000, 
#             variable_costs_per_unit = 5000,
#             profit_per_unit = 95000)
# 
# plot_profit(data = plot_input_svm, 
#             fixed_costs = 100000, 
#             variable_costs_per_unit = 5000,
#             profit_per_unit = 95000)
 

WORKFLOWSETS

LS0tDQp0aXRsZTogIlByZWRpY3RpdmUgQXR0cml0aW9uIE1vZGVsICYgUk9JIEFuYWx5c2lzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyBPYmplY3RpdmUNCg0KVG8gYnVpbGQgYSBwcmVkaWN0aXZlIG1vZGVsIGZvciBhdHRyaXRpb24gdGhhdCBwcm92aWRlcyB1c2VmdWwgaW5wdXQgaW50byBhbiBST0kgY2FsY3VsYXRpb24gZm9yIHJldGVudGlvbiBpbnRlcnZlbnRpb24ocykuIA0KDQojIFN1bW1hcnkgJiBDb25jbHVzaW9ucyANCg0KVXNpbmcgdGhlIHByb3ZpZGVkIGRhdGEgd2hpY2ggaW5jbHVkZWQgdmFyaWFibGVzIHN1Y2ggYXMgZW1wbG95ZWUgc2F0aXNmYWN0aW9uIGxldmVscywgbGFzdCBldmFsdWF0aW9uIHNjb3JlLCBudW1iZXIgb2YgcHJvamVjdHMsIGF2ZXJhZ2UgaG91cnMgd29ya2VkIGluIGEgbW9udGgsIHRlbnVyZSwgcHJvbW90aW9ucywgYW5kIHNhbGFyeSwgd2Ugc291Z2h0IHRvIGZpbmQgYSBtb2RlbCB0aGF0IGNvdWxkIGFjY3VyYXRlbHkgcHJlZGljdCB3aGljaCBlbXBsb3llZXMgbWF5IGxlYXZlIHRoZSBjb21wYW55LiBHaXZlbiB0aGUgdmFyaWFibGVzIGluIHRoZSBkYXRhLCBvdXIgZ29hbCB3YXMgdG8gZGV0ZXJtaW5lIHdoaWNoIHByZWRpY3RvcnMgY291bGQgcHJvdmlkZSB0aGUgbW9zdCBzaWduYWwgdGhhdCBhbiBlbXBsb3llZSBpbnRlbmRlZCB0byBsZWF2ZS4gSWYgdGhpcyBpbmZvcm1hdGlvbiB3YXMga25vd24sIHdlIG1heSBiZSBhYmxlIHRvIGludGVydmVuZSBhbmQgcHJldmVudCB0aGUgZW1wbG95ZWUgZnJvbSBsZWF2aW5nLCB0aGVyZWJ5IHNhdmluZyB0aGUgY29tcGFueSBtb25leS4gDQoNClNlZSB0aGUgc2VjdGlvbiBjYWxsZWQgW0J1c2luZXNzIFZhbHVlOiBWaXN1YWxpemluZyBNb2RlbCBQZXJmb3JtYW5jZV0gYXQgdGhlIGJvdHRvbSBvZiB0aGlzIGRvY3VtZW50IGZvciB2aXN1YWxpemF0aW9ucyBvZiB0aGUgUk9JIGRpc2N1c3NlZCBoZXJlLiBfd2VsbCBhY3R1YWxseSwgYnVncyBpbiB0aGUgcGFja2FnZSBwcmV2ZW50ZWQgdGhpcyBsaXR0bGUgYml0IG9mIG1hZ2ljLiBtYXliZSBuZXh0IHRpbWVfDQoNCiMjIFByb3Bvc2VkIE1vZGVsDQpUaGUgbW9kZWwgcHJvcG9zZWQgaXMgYSBSYW5kb20gRm9yZXN0IG1vZGVsLiBUaGUgbW9kZWwgdXNlcyByYW5kb20gY29tYmluYXRpb25zIG9mIHByZWRpY3RvciB2YXJpYWJsZXMgdG8gZm9ybSBhIGRlY2lzaW9uIHRyZWUgYmFzZWQgcHJlZGljdGlvbiBmb3IgdGhlIG91dGNvbWUgdmFyaWFibGUuIA0KDQpUaGUgdmFyaWFibGUgaW1wb3J0YW5jZSBzdGF0aXN0aWNzIGZvciB0aGUgUkYgbW9kZWwgYXJlIGFzZSBmb2xsb3dzOg0KDQogICAgUHJlZGljdG9yICAgICAgICB8ICAgICAgICAgMCB8ICAgICAgICAgMSB8IE1lYW5EZWNyZWFzZUFjY3VyYWN5IHwgTWVhbkRlY3JlYXNlR2luaQ0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tfCAtLS0tLS0tLS0gfCAtLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0tLS0gDQpzYXRpc2ZhY3Rpb25fbGV2ZWwgIHwgIDEwMS40MDAzNDQgfCAzMzcuODk4NTMwICB8ICAgICAgICAgMzE5Ljg4ODM1MCAgfCAgIDExMjQuNzU5OTM0DQpsYXN0X2V2YWx1YXRpb24gICAgICB8ICAxOC42NDAwNDQgfCAxMTEuMDM1MzY4ICB8ICAgICAgICAxMTMuNzEwMDk4ICAgfCAgICAzMDIuMjAwMTMzDQpudW1iZXJfcHJvamVjdCAgICAgICB8ICA2OS42OTQyMTggfCAyMzkuMTYwNTk3ICB8ICAgICAgICAgMjM1LjMzMzU4MyAgfCAgICA0NDUuNTYwMjE5DQphdmVyYWdlX21vbnRobHlfaG91cnMgfCA2Mi41ODYyNDMgfCAxMDEuNDE2MzM4ICB8ICAgICAgICAgMTExLjkxMDA2MiAgfCAgICAzMjguNjM3NTQ3DQp0aW1lX3NwZW5kX2NvbXBhbnkgICAgfCA1Ny45MzkxNzEgfCA4Mi40MTgyNzQgICB8ICAgICAgICAgOTEuNjY4NTc1ICAgfCAgICA0NTkuNjIxNjMzDQpXb3JrX2FjY2lkZW50ICAgICAgICAgfCAgNC4zMTk4MjkgfCA4LjY4NDUyMSAgICB8ICAgICAgICAgOS4wOTI5NDMgICAgfCAgICAgIDQuNTkxOTMyDQpwcm9tb3Rpb25fbGFzdF81eWVhcnMgfCAgMi44MzA3OTcgfCAgNS4xNjA1NDUgICB8ICAgICAgICAgIDUuMDY0NjMyICAgfCAgICAgIDEuMDc2NTk2DQpkZXBhcnRtZW50ICAgICAgICAgICB8ICAgNy41ODAyNzYgfCA0OC4yOTMzNzQgICB8ICAgICAgICAgMjkuMDYwNjU2ICAgfCAgICAgMjkuNzc1NjQ2DQpzYWxhcnkgICAgICAgICAgICAgICB8ICAgNy4yMTA3NzggfCAyNy41Mzc1NzcgICB8ICAgICAgICAgMTkuODUzMjg1ICAgfCAgICAgMTMuMDk0MTQ1DQoNCg0KRXhhbWluaW5nIHRoZXNlIHN0YXRzLCB3ZSBzZWUgdGhhdCBgc2F0aXNmYWN0aW9uX2xldmVsYCwgYGxhc3RfZXZhbHVhdGlvbmAsIGBudW1iZXJfcHJvamVjdGAgdmFyaWFibGVzIGdpdmUgdGhlIG1vc3Qgc2lnbmFsIChha2EsIGhhdmUgdGhlIG1vc3QgaW5mbHVlbmNlKSBhcyBmYWN0b3JzIGluZGljYXRpbmcgYW4gZW1wbG95ZWUgbWF5IGxlYXZlIHRoZSBjb21wYW55LiBBcyB0aGVzZSB2YWx1ZXMgaW5jcmVhc2UsIHNvIHRvbyBkb2VzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBlbXBsb3llZSB3aWxsIHF1aXQuICBUaGUgcHJvcG9zZWQgbW9kZWwgaGFzIGFuIGFjY3VyYWN5IG9mIGA5OC40JWAgZm9yIHRoZSB0ZXN0IGRhdGFzZXQuIA0KDQpJdCBpcyBpbXBvcnRhbnQgdG8gbm90ZSB0aGF0IHRoaXMgbW9kZWwgaXMgdmFsaWQgZm9yIHRoZSB0ZXN0IGRhdGEgc2V0LCBidXQgbWF5IG5vdCBnZW5lcmFsaXplIHdlbGwgdG8gZnV0dXJlIGRhdGEuIFRoZSBwcm9wb3NlZCBtb2RlbCBzaG91bGQgYmUgd2VsbCB2YWxpZGF0ZWQgYWdhaW5zdCBuZXcgZGF0YXNldHMgYmVmb3JlIHByb2NlZWRpbmcuIFRoYXQgc2FpZCwgdGhlIFJhbmRvbSBGb3Jlc3QgYXBwcm9hY2ggc2hvdWxkIGJlIG1vcmUgZ2VuZXJhbGl6YWJsZSB0aGFuIGEgc3RhbmRhcmQgRGVjaXNpb24gVHJlZSBtb2RlbCBiYXNlZCBvbiB0aGUgdW5kZXJseWluZyBzdGF0aXN0aWNhbCBwcm9wZXJ0aWVzIG9mIHRoZSANCg0KQ29uZmlkZW5jZSB3YXMgY2hlY2tlZCB0aHJvdWdoIGNyb3NzLXZhbGlkYXRpb24gb2YgMTAgay1mb2xkcyBpbiB0aGUgZGF0YSBzZXQuIFRvIGdhaW4gZnVydGhlciBjb25maWRlbmNlLCBmdXR1cmUgYW5hbHlzdHMgbWF5IHdpc2ggdG8gaW5jcmVhc2UgdGhlIG51bWJlciBvZiBib290c3RyYXAgZGF0YXNldHMgdGhleSB0ZXN0IHRvIGluY3JlYXNlIGNvbmZpZGVuY2UgaW4gdGhlIG92ZXJhbGwgbW9kZWwgYXMgd2VsbCBhcyBnYWluIG1vcmUgc2lnbmFsIG9uIHNwZWNpZmljIGZlYXR1cmVzIChha2EgcHJlZGljdG9yIHZhcmlhYmxlcykgb2YgdGhlIG1vZGVsLiANCg0KIyMgUk9JIG9mIEludGVydmVudGlvbg0KDQpVbmZvcnR1bmF0ZWx5LCBSYW5kb20gRm9yZXN0cyBjYW4gYmUgZGlmZmljdWx0IHRvIGludGVycHJldCwgc28gdGhlcmUgaXMgbm90IGEgZGlyZWN0IGludGVydmVudGlvbiB0aGF0IGNhbiBiZSBwcm9wb3NlZCBiYXNlZCBvbiB0aGlzIG1vZGVsLiBIb3dldmVyLCBpZiB3ZSBmb2N1c2VkIG9uIHRoZSBsZWFkaW5nIHZhcmlhYmxlIC0gc2F0aXNmYWN0aW9uIGxldmVsIC0gYW5kIHJhbiBhbiBpbnRlcnZlbnRpb24gdG8gaW1wcm92ZSBzYXRpc2ZhY3Rpb24sIHdlIGNhbiB1bmRlcnN0YW5kIGhvdyBtdWNoIG1vbmV5IG1heSBiZSBzYXZlZC4gDQoNCkZvciB0aGlzIG9iamVjdGl2ZSwgaXQgaGFzIGJlZW4gZGV0ZXJtaW5lZCB0aGF0IHRoZSBsb3NzIG9mIGFuIGVtcGxveWVlIGNvc3RzIHRoZSBjb21wYW55IDEwMCwwMDAgZG9sbGFycy4gV2UgYXJlIHJlY29tbWVuZGluZyBhbiBpbnRlcnZlbnRpb24gcHJvZ3JhbSB3aXRoIGEgY29zdCBvZiA1LDAwMCBkb2xsYXJzIHBlciBlbXBsb3llZS4gQmFzZWQgb24gb3VyIHJlc2VhcmNoIGFuZCBkYXRhLCB3ZSBleHBlY3QgdGhlIGludGVydmVudGlvbiBwcm9ncmFtIHRvIHdvcmsgYXBwcm94aW1hdGVseSA1MCUgb2YgdGhlIHRpbWUuIFdpdGggdGhlc2UgY29zdHMgaW4gbWluZCwgaXQgaXMgdW5kZXJzdG9vZCB0aGF0IHRoZSBjb21wYW55IHdpbGwgc2F2ZSA5NSwwMDAgZG9sbGFycyBwZXIgZW1wbG95ZWUgd2hvIGRvZXMgbm90IGxlYXZlIGFzIGEgcmVzdWx0IG9mIHRoZSBpbnRlcnZlbnRpb24uDQoNCkluIHRoZSBmaW5hbCBydW5uaW5nIG9mIHRoZSBtb2RlbCwgaXQgd2FzIGFjY3VyYXRlbHkgcHJlZGljdGVkIHRoYXQgMTY5MyBwZW9wbGUgd291bGQgbGVhdmUgdGhlIGNvbXBhbnkuIEFzc3VtaW5nIHdlIGNhbiBzYXZlIGhhbGYgb2YgdGhvc2UgcGVvcGxlIHdpdGggb3VyIGludGVydmVudGlvbiAobj04NDcpLCB0aGUgY29tcGFueSB3aWxsIGhhdmUgc2F2ZWQgODAuNCBNIGRvbGxhcnMsIG1pbnVzIHRoZSBjb3N0IG9mIHRoZSBpbnRlcnZlbnRpb24gZm9yIHRob3NlIHdobyB3ZXJlbid0IHNhdmVkICg0LjJNKSwgYSB0b3RhbCBvZiA3Ni4yTSBkb2xsYXJzLiANCg0KV2l0aCA5OC44JSBhY2N1cmFjeSBpbiB0aGUgbW9kZWwgYW5kIA0KDQoqQ29uZnVzaW9uIE1hdHJpeCogZm9yIGByZl9maW5hbGAgDQpPT0IgZXN0aW1hdGUgb2YgIGVycm9yIHJhdGU6IDEuMTklDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQogICAgIDAgICAgMSBjbGFzcy5lcnJvcg0KMCA1NzE3ICAgIDkgMC4wMDE1NzE3NzgNCjEgICA4MCAxNjkzIDAuMDQ1MTIxMjYzDQoNCg0KDQoNCg0KIyMgRm9yIEZ1dHVyZSBBbmFseXN0cyBVc2luZyB0aGlzIENvZGUNCg0KVGhlIGZvbGxvd2luZyBjb2RlIHdhcyB1c2VkIHRvIHJ1biBhbmQgaW50ZXJwcmV0IDQgbW9kZWxzIHRvIHByZWRpY3QgYXR0cml0aW9uIHVzaW5nIHRoZSBwcm92aWRlZCBkYXRhc2V0LiBUaGUgZmlyc3Qgc2VjdGlvbnMgaW5jbHVkZSBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9ucyB0byB1bmRlcnN0YW5kIHRoZSBjb250ZXh0IG9mIHRoZSBkYXRhLiBUaGUgZGF0YSB3YXMgcmVsYXRpdmVseSDigJxjbGVhbuKAnSB3aXRoIG5vIG1pc3NpbmcgdmFsdWVzIG9yIGR1cGxpY2F0ZWQgb2JzZXJ2YXRpb25zLiBNb2RlbHMgd2VyZSBkZXZlbG9wZWQgdXNpbmcgYHRpZHlgIG1vZGVsaW5nIHByaW5jaXBsZXMsIGluY2x1ZGluZyB3b3JrZmxvd3MgYW5kIHJlY2lwZXMuIFRoZSByZWNpcGVzIGluY2x1ZGUgcHJlcHJvY2Vzc2luZyBzdGVwcyB0byBub3JtYWxpemUgYW5kIGJhbGFuY2UgdGhlIGRhdGEuIA0KDQpBIHRhYmxlIG9mIG1vZGVsIHJlc3VsdHMgYW5kIGRlY2lzaW9ucyBhcmUgYmVsb3cgKG1vcmUgZGV0YWlscyBjYW4gYmUgZm91bmQgaW4gdGhlIGBtb2RlbF9jb21wYCB0YWJsZSkuIFRoZSBhcHByb2FjaCBhdHRlbXB0ZWQgc2V2ZXJhbCBtb2RlbHMgd2l0aCB2YXJ5aW5nIGxldmVscyBvZiBjb21wbGV4aXR5LiBUaGUgc2ltcGxlciBtb2RlbHMgZGlkIG5vdCBwZXJmb3JtIHdlbGwgYW5kIGRpZCBub3QgcHJlZGljdCBhYm92ZS9iZXlvbmQgY2hhbmNlLiBUaGUgbW9yZSBjb21wbGV4IG1vZGVscyBwZXJmb3JtIHZlcnkgd2VsbCBvbiB0aGUgYXZhaWxhYmxlIGRhdGFzZXQsIGJ1dCBhcmUgZGlmZmljdWx0IHRvIGludGVycHJldCBmb3IgdGhlIGJ1c2luZXNzLiANCg0KQ0FWRUFUOiBUaGUgcHJvcG9zZWQgbW9kZWwgc2hvdWxkIGJlIHdlbGwgdmFsaWRhdGVkIGFnYWluc3QgbmV3IGRhdGFzZXRzIGJlZm9yZSBwcm9jZWVkaW5nLiBUaGUgUmFuZG9tIEZvcmVzdCBhcHByb2FjaCBzaG91bGQgYmUgbW9yZSBnZW5lcmFsaXphYmxlIHRoYW4gYSBzdGFuZGFyZCBEZWNpc2lvbiBUcmVlLiANCg0KTW9kZWwgIHwgQWNjdXJhY3kgfCBEZWNpc2lvbg0KLS0tLS0tLXwgLS0tLS0tLS0gfCAtLS0tLS0tDQpMb2dpc3RpYyBSZWdyZXNzaW9uICB8IDAuNzQ3MzA0MCAgfCBMb3cgYWNjdXJhY3kNClN1cHBvcnQgVmVjdG9yIENsYXNzICB8IDAuNzU1NTcwMiB8IExvdyBhY2N1cmFjeQ0KTmV1cmFsIE5ldCAgfCAwLjkzNjEyNTEgfCBIaWdoIGFjY3VyYWN5LCB1bmFibGUgdG8gaW50ZXJwcmV0IHJlc3VsdHMNClJhbmRvbSBGb3Jlc3QgIHwgMC45ODQ2NjM2ICB8IEhpZ2ggYWNjdXJhY3ksIHVuYWJsZSB0byBpbnRlcnByZXQgcmVzdWx0cw0KDQojIEZpbGUgU2V0dXANCg0KIyMgTGlicmFyaWVzICYgQ29uZmxpY3RzDQoNCg0KYGBge3IgTGlicmFyaWVzLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoew0KbGlicmFyeShyZWFkeGwpDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeShNQVNTKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoUkN1cmwpDQpsaWJyYXJ5KERUKQ0KbGlicmFyeShtb2RlbHIpDQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkocFJPQykNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoVklNKQ0KbGlicmFyeShEVCkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoTWV0cmljcykNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShrZXJhcykgI25ldXJhbCBuZXQNCmxpYnJhcnkoZTEwNzEpDQpsaWJyYXJ5KGR0cmVlKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoRE13UjIpDQpsaWJyYXJ5KHJzYW1wbGUpDQpsaWJyYXJ5KHNraW1yKQ0KbGlicmFyeShwc3ljaCkNCmxpYnJhcnkoY29uZmxpY3RlZCkNCmxpYnJhcnkodHJlZSkNCmxpYnJhcnkodGlkeW1vZGVscykNCmxpYnJhcnkoamFuaXRvcikNCmxpYnJhcnkoc2tpbXIpDQpsaWJyYXJ5KEdHYWxseSkNCmxpYnJhcnkodGlkeXF1YW50KQ0KbGlicmFyeShkb1BhcmFsbGVsKSANCmxpYnJhcnkodGhlbWlzKQ0KbGlicmFyeShmdW5Nb2RlbGluZykgI0RhdGEgdmlzdWFsaXphdGlvbg0KbGlicmFyeShEYXRhRXhwbG9yZXIpICNEYXRhIHZpc3VhbGl6YXRpb24NCmxpYnJhcnkoQ0dQZnVuY3Rpb25zKSAjRGF0YSB2aXN1YWxpemF0aW9uIC0gY3Jvc3MgdGFicw0KbGlicmFyeSh0aWR5bG9nLCB3YXJuLmNvbmZsaWN0cyA9IEZBTFNFKQ0KfSkNCg0KY29uZmxpY3RfcHJlZmVyKCJnYXRoZXIiLCAidGlkeXIiKQ0KY29uZmxpY3RfcHJlZmVyKCJwaXZvdF9sb25nZXIiLCAidGlkeXIiKQ0KY29uZmxpY3RfcHJlZmVyKCJwaXZvdF93aWRlciIsICJ0aWR5ciIpDQpjb25mbGljdF9wcmVmZXIoInR1bmUiLCAidHVuZSIpDQpjb25mbGljdF9wcmVmZXIoInNlbGVjdCIsICJkcGx5ciIpDQpjb25mbGljdF9wcmVmZXIoInN1bW1hcmlzZSIsICJkcGx5ciIpDQpjb25mbGljdF9wcmVmZXIoIm11dGF0ZV9pZiIsICJkcGx5ciIpDQpjb25mbGljdF9wcmVmZXIoInNlbGVjdF9pZiIsICJkcGx5ciIpDQpjb25mbGljdF9wcmVmZXIoIm11dGF0ZSIsICJkcGx5ciIpDQpjb25mbGljdF9wcmVmZXIoImdyb3VwX2J5IiwgImRwbHlyIikNCmNvbmZsaWN0X3ByZWZlcigiZmlsdGVyIiwgImRwbHlyIikNCmNvbmZsaWN0X3ByZWZlcigicmVuYW1lIiwgImRwbHlyIikNCmNvbmZsaWN0X3ByZWZlcigiZGlzdGluY3QiLCAiZHBseXIiKQ0KY29uZmxpY3RfcHJlZmVyKCJzdGVwX3Vwc2FtcGxlIiwgInJlY2lwZXMiKQ0KY29uZmxpY3RfcHJlZmVyKCJjaGlzcS50ZXN0IiwgInN0YXRzIikNCmNvbmZsaWN0X3ByZWZlcigic2tld25lc3MiLCAiUGVyZm9ybWFuY2VBbmFseXRpY3MiKQ0KY29uZmxpY3RfcHJlZmVyKCJmaXQiLCAicGFyc25pcCIpDQpjb25mbGljdF9wcmVmZXIoInJtc2UiLCAieWFyZHN0aWNrIikNCmNvbmZsaWN0X3ByZWZlcigicmVjYWxsIiwgInlhcmRzdGljayIpDQpjb25mbGljdF9wcmVmZXIoInByZWNpc2lvbiIsICJ5YXJkc3RpY2siKQ0KY29uZmxpY3RfcHJlZmVyKCJmX21lYXMiLCAieWFyZHN0aWNrIikNCmNvbmZsaWN0X3ByZWZlcigiYWNjdXJhY3kiLCAieWFyZHN0aWNrIikNCmNvbmZsaWN0X3ByZWZlcigia2FwIiwgInlhcmRzdGljayIpDQpjb25mbGljdF9wcmVmZXIoInJvY19hdWMiLCAieWFyZHN0aWNrIikNCmNvbmZsaWN0X3ByZWZlcigic2VucyIsICJ5YXJkc3RpY2siKQ0KY29uZmxpY3RfcHJlZmVyKCJzcGVjIiwgInlhcmRzdGljayIpDQpjb25mbGljdF9wcmVmZXIoIm1hcCIsICJwdXJyciIpDQpjb25mbGljdF9wcmVmZXIoInZpcCIsICJ2aXAiKQ0KY29uZmxpY3RfcHJlZmVyKCJkZXNjcmliZSIsICJwc3ljaCIpDQoNCmZvciAoZiBpbiBnZXROYW1lc3BhY2VFeHBvcnRzKCJ0aWR5bG9nIikpIHsNCiAgICBjb25mbGljdGVkOjpjb25mbGljdF9wcmVmZXIoZiwgInRpZHlsb2ciLCBxdWlldCA9IFRSVUUpDQp9DQpgYGANCg0KYGBge3IgRmFpbGVkIEJ1ZyBGaXgsIGluY2x1ZGUgPSBGQUxTRX0NCiNKVUxJQSBTSUxHRSBUSElOS1MgVEhFUkUgSVMgQSBCVUcgSU4gVFVORSAmIFJFQ0lQRVMgS0VFUElORyBTT01FIE9GIE1ZIE1PREVMUyBGUk9NIFdPUktJTkcuIFdPUktBUk9VTkQgSVMgVE8gTE9BRCBQQUNLQUdFUyBGUk9NIEdJVEhVQiwgQlVUIFRISVMgRElETidUIFNFRU0gVE8gV09SSyBFSVRIRVIuDQojbGlicmFyeShkZXZ0b29scykNCiMgaW5zdGFsbF9naXRodWIoInRpZHltb2RlbHMvdHVuZSIpDQojIGluc3RhbGxfZ2l0aHViKCJ0aWR5bW9kZWxzL3JlY2lwZXMiKQ0KDQojIGxpYnJhcnkodGlkeXZlcnNlKQ0KIyBsaWJyYXJ5KHRpZHltb2RlbHMpDQojIGxpYnJhcnkoZG9GdXR1cmUpDQojIA0KIyByZWdpc3RlckRvRnV0dXJlKCkNCiMgYWxsX2NvcmVzIDwtIGRldGVjdENvcmVzKGxvZ2ljYWwgPSBGQUxTRSkNCiMgY2wgPC0gcGFyYWxsZWw6Om1ha2VDbHVzdGVyKGFsbF9jb3JlcykNCiMgcGxhbihjbHVzdGVyLCB3b3JrZXJzID0gY2wpDQpgYGANCg0KIyMgSGVscGVyIEZ1bmN0aW9ucw0KYGBge3IgVmlzdWFsaXphdGlvbiBGdW5jdGlvbnMsIGluY2x1ZGUgPSBGQUxTRX0NCiNGcm9tIE1hdHQgRGFuY2hvIERTNEIgMjAxDQoNCnBsb3RfZ2dwYWlycyA8LSBmdW5jdGlvbihkYXRhLCBjb2xvciA9IE5VTEwsIGRlbnNpdHlfYWxwaGEgPSAwLjUpIHsNCiAgICANCiAgICBjb2xvcl9leHByIDwtIGVucXVvKGNvbG9yKQ0KICAgIA0KICAgIGlmIChybGFuZzo6cXVvX2lzX251bGwoY29sb3JfZXhwcikpIHsNCiAgICAgICAgDQogICAgICAgIGcgPC0gZGF0YSAlPiUNCiAgICAgICAgICAgIGdncGFpcnMobG93ZXIgPSAiYmxhbmsiKSANCiAgICAgICAgDQogICAgfSBlbHNlIHsNCiAgICAgICAgDQogICAgICAgIGNvbG9yX25hbWUgPC0gcXVvX25hbWUoY29sb3JfZXhwcikNCiAgICAgICAgDQogICAgICAgIGcgPC0gZGF0YSAlPiUNCiAgICAgICAgICAgIGdncGFpcnMobWFwcGluZyA9IGFlc19zdHJpbmcoY29sb3IgPSBjb2xvcl9uYW1lKSwgDQogICAgICAgICAgICAgICAgICAgIGxvd2VyID0gImJsYW5rIiwgbGVnZW5kID0gMSwNCiAgICAgICAgICAgICAgICAgICAgZGlhZyA9IGxpc3QoY29udGludW91cyA9IHdyYXAoImRlbnNpdHlEaWFnIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gZGVuc2l0eV9hbHBoYSkpKSArDQogICAgICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCiAgICB9DQogICAgDQogICAgcmV0dXJuKGcpDQogICAgDQp9DQoNCiNGcm9tIE1hdHQgRGFuY2hvIERTNEIgMjAxDQpwbG90X2hpc3RfZmFjZXQgPC0gZnVuY3Rpb24oZGF0YSwgZmN0X3Jlb3JkZXIgPSBGQUxTRSwgZmN0X3JldiA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBiaW5zID0gMTAsIGZpbGwgPSBwYWxldHRlX2xpZ2h0KClbWzNdXSwgY29sb3IgPSAid2hpdGUiLCBuY29sID0gNSwgc2NhbGUgPSAiZnJlZSIpIHsNCiAgICANCiAgICBkYXRhX2ZhY3RvcmVkIDwtIGRhdGEgJT4lDQogICAgICAgIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgJT4lDQogICAgICAgIG11dGF0ZV9pZihpcy5mYWN0b3IsIGFzLm51bWVyaWMpICU+JQ0KICAgICAgICBnYXRoZXIoa2V5ID0ga2V5LCB2YWx1ZSA9IHZhbHVlLCBmYWN0b3Jfa2V5ID0gVFJVRSkgDQogICAgDQogICAgaWYgKGZjdF9yZW9yZGVyKSB7DQogICAgICAgIGRhdGFfZmFjdG9yZWQgPC0gZGF0YV9mYWN0b3JlZCAlPiUNCiAgICAgICAgICAgIG11dGF0ZShrZXkgPSBhcy5jaGFyYWN0ZXIoa2V5KSAlPiUgYXMuZmFjdG9yKCkpDQogICAgfQ0KICAgIA0KICAgIGlmIChmY3RfcmV2KSB7DQogICAgICAgIGRhdGFfZmFjdG9yZWQgPC0gZGF0YV9mYWN0b3JlZCAlPiUNCiAgICAgICAgICAgIG11dGF0ZShrZXkgPSBmY3RfcmV2KGtleSkpDQogICAgfQ0KICAgIA0KICAgIGcgPC0gZGF0YV9mYWN0b3JlZCAlPiUNCiAgICAgICAgZ2dwbG90KGFlcyh4ID0gdmFsdWUsIGdyb3VwID0ga2V5KSkgKw0KICAgICAgICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gYmlucywgZmlsbCA9IGZpbGwsIGNvbG9yID0gY29sb3IpICsNCiAgICAgICAgZmFjZXRfd3JhcCh+IGtleSwgbmNvbCA9IG5jb2wsIHNjYWxlID0gc2NhbGUpICsgDQogICAgICAgIHRoZW1lX3RxKCkNCiAgICANCiAgICByZXR1cm4oZykNCiAgICANCn0NCmBgYA0KDQotLS0NCiMjIExvYWRpbmcgRGF0YQ0KDQpgYGB7ciBEYXRhIGltcG9ydH0NCmxpYnJhcnkocmVhZHhsKQ0KRGF0YSA8LSByZWFkX2V4Y2VsKCJDOi9Vc2Vycy9KYWNseW4vRGVza3RvcC9HaXRodWJDbG9uZS9wc3ljNjg0MV9maW5hbHByb2plY3QvMDFfZGF0YS9QU1lDNjg0MV9GaW5hbF9Qcm9qZWN0X0RhdGEueGxzeCIpDQoNCnN0cihEYXRhKQ0KYGBgDQoNCiMgRGF0YSBQcmVwcm9jZXNzaW5nICYgUmV2aWV3DQoNCiMjIE92ZXJ2aWV3DQoNCmBgYHtyIFNraW19DQpza2ltKERhdGEpDQpgYGANCg0KVGhlIGRhdGFzZXQgaGFzIDksOTk5IHJvd3MsIDEwIGNvbHVtbnMsIDIgZmFjdG9yIGNvbHVtbnMsIDggbnVtZXJpYyBjb2x1bW5zLCBhbmQgbm8gbWlzc2luZyB2YWx1ZXMgKG5fbWlzc2luZykgYWNyb3NzIGFsbCBjb2x1bW5zIG9mIHRoZSBkYXRhIGZyYW1lLg0KDQpgYGB7ciBHbGltcHNlfQ0KZ2xpbXBzZShEYXRhKQ0KYGBgDQoNCg0KYGBge3IgQ29sIE5hbWVzfQ0KY29sbmFtZXMoRGF0YSkNCmBgYA0KDQpgYGB7ciBDaGFyYWN0ZXIgVW5pcXVlfQ0KI1ZJRVdJTkcgQ0hBUkFDVEVSIERBVEEgVkFMVUVTDQpEYXRhICU+JQ0KICAgIHNlbGVjdF9pZihpcy5jaGFyYWN0ZXIpICU+JSANCiAgICBtYXAodW5pcXVlKSAjZnJvbSBwdXJyciAtLSBzaG93cyB1cyBhbGwgb2YgdGhlIHZhbHVlcyBhdmFpbGFibGUgZm9yIG91ciBjaGFyYWN0ZXIgZGF0YQ0KYGBgDQoNClRoZSBgc2FsZXNgIGNvbHVtbiBhcHBlYXJzIHRvIGJlIG1pc2xhYmVsbGVkLiBXZSdsbCByZW5hbWUgaXQgIkRlcGFydG1lbnQiIGJhc2VkIG9uIHRoZSB2YWx1ZXMgd2Ugc2VlIGluIHRoYXQgY29sdW1uLiANCg0KYGBge3IgUmVuYW1lIFNhbGVzfQ0KI1JFTkFNRSAnU0FMRVMnIENPTFVNTiBUTyBgREVQQVJUTUVOVGANCkRhdGEgPC0gcmVuYW1lKERhdGEsIGRlcGFydG1lbnQgPSBzYWxlcykNCmBgYA0KDQojIyBEYXRhIFN0cnVjdHVyZXMNCg0KYGBge3IgR2xpbXBzZTJ9DQojRE9VQkxFIENIRUNLIEZBQ1RPUg0KZ2xpbXBzZShEYXRhKQ0KYGBgDQoNCg0KYGBge3IgVmlldyBOdW1lcmljIFVuaXF1ZXN9DQojVEFCTEUgT0YgTlVNQkVSIFVOSVFVRSBWQUxVRVMgRk9SIE5VTUVSSUMgVkFSSUFCTEVTDQpEYXRhICU+JQ0KICAgIHNlbGVjdF9pZihpcy5udW1lcmljKSAlPiUNCiAgICBtYXBfZGYofiB1bmlxdWUoLikgJT4lIGxlbmd0aCgpKSAlPiUgIyB0cmllcyB0byB0dXJuIGl0IGludG8gYSBkZiBpbnN0ZWFkIG9mIGEgbGlzdA0KICAgIGdhdGhlcigpICU+JSAgDQogIGFycmFuZ2UoZGVzYyh2YWx1ZSkpDQpgYGANCg0KYGBge3IgRHVtbXkgQ29kZWR9DQojVklFVyBEVU1NWSBDT0RFRCBWQVJJQUJMRVMNCkRhdGEgJT4lIA0KICBkaXN0aW5jdChXb3JrX2FjY2lkZW50LCBsZWZ0LCBwcm9tb3Rpb25fbGFzdF81eWVhcnMpDQpgYGANCg0KYGBge3IgVmlldyBTYWxhcnl9DQojVklFVyBMRVZFTFMgT0YgU0FMQVJZDQpEYXRhICU+JSANCiAgZGlzdGluY3Qoc2FsYXJ5KQ0KYGBgDQpUaGUgYHNhbGFyeWAgZGF0YSBuZWVkcyB0byBiZSB0dXJuZWQgaW50byBhbiBvcmRlcmVkIGZhY3Rvci4gV2UnbGwgb3JkZXIgdGhlIGxldmVscyBmcm9tIGBMb3dgIHRvIGBIaWdoYC4gDQoNCmBgYHtyIE11dGF0aW5nIFNhbGFyeSBGYWN0b3J9DQojQ0hBTkdFIFNBTEFSWSBUTyBPUkRFUkVEIEZBQ1RPUg0KRGF0YSA8LSBEYXRhICU+JQ0KICBtdXRhdGUoc2FsYXJ5ID0gZmFjdG9yKHNhbGFyeSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoImxvdyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtZWRpdW0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaGlnaCIpKSkNCmBgYA0KDQpUaHJlZSBvZiBvdXIgbnVtZXJpYyB2YXJpYWJsZXMgYXBwZWFyIHRvIGJlIGR1bW15IGNvZGVkICgwPU5vLzE9WWVzKSwgaW5jbHVkaW5nIGBXb3JrX2FjY2lkZW50YCwgYGxlZnRgLCBgcHJvbW90aW9uX2xhc3RfNXllYXJzYC4gDQoNCk9uZSBvZiB0aGVzZSB2YXJpYWJsZXMsIGBMZWZ0YCwgaXMgb3VyIG91dGNvbWUgdmFyaWFibGUsIGRldGVybWluaW5nIGlmIHNvbWVvbmUgc3RheWVkIGF0IG9yIGxlZnQgdGhlIGNvbXBhbnkuIA0KDQpUaHJlZSBvZiBvdXIgbnVtZXJpYyB2YXJpYWJsZXMgYXBwZWFyIHRvIGJlIGNvbnRpbnVvdXMsIGluY2x1ZGluZyBgYXZlcmFnZV9tb250aGx5X2hvdXJzYCwgYHNhdGlzZmFjdGlvbl9sZXZlbGAsIGFuZCBgbGFzdF9ldmFsdWF0aW9uYC4gDQoNClR3byBudW1lcmljIHZhcmlhYmxlcyBhcmUgZGlzY3JldGUsIGluY2x1ZGluZyBvbmx5IDYgKGBudW1iZXJfcHJvamVjdGApIG9yIDggKGB0aW1lX3NwZW5kX2NvbXBhbnlgKSB2YWx1ZXMgZWFjaC4NCg0KDQojIyBNaXNzaW5nbmVzcyAmIER1cGxpY2F0ZXMNCg0KYGBge3IgTWlzc2luZ25lc3MgTWFwfQ0KI0NIRUNLIEZPUiBNSVNTSU5HIERBVEENCmxpYnJhcnkoQW1lbGlhKQ0KbWlzc21hcChEYXRhLCB5LmF0PWMoMSksIHkubGFiZWxzPWMoJycpLCBjb2w9Yygnb3JhbmdlJywgJ3B1cnBsZScpKQ0KYGBgDQoNCldvdywgbm8gbWlzc2luZyB2YXJpYWJsZXMhIFdlIHdpbGwgY29uc2lkZXIgb3VyIGRhdGFzZXQgd2hvbGUgYW5kIGRvIG5vdCBuZWVkIHRvIGltcHV0ZSBhbnkgbWlzc2luZyBkYXRhLiANCg0KV2UnbGwgYWxzbyBjaGVjayBpZiB0aGVyZSBhcmUgYW55IGR1cGxpY2F0ZSBsaW5lcyB3ZSBuZWVkIHRvIGJlIGF3YXJlIG9mLiANCg0KYGBge3IgRHVwbGljYXRlIFJvd3N9DQojQ0hFQ0sgRk9SIFJPV1MgV0lUSCBFWEFDVCBTQU1FIFZBTFVFUw0Kc3VtKGlzLm5hKGR1cGxpY2F0ZWQoRGF0YSkpKQ0KYGBgDQoNCmBgYHtyIER1cGxpY2F0ZSBDb2x1bW5zfQ0KI0lTIE9VUiBJRCBDT0xVTU4gS09TSEVSPw0Kd2hpY2goZHVwbGljYXRlZChEYXRhJElEKSkNCmBgYA0KVGhlcmUgZG8gbm90IGFwcGVhciB0byBiZSBhbnkgZHVwbGljYXRlIHJvd3Mgb3IgY29sdW1uIGRhdGEuIA0KDQojIyBTdW1tYXJ5IFN0YXRpc3RpY3MgDQoNCmBgYHtyIERlc2NyaWJlfQ0KcHN5Y2g6OmRlc2NyaWJlKERhdGEpDQpgYGANCkF0IGZpcnN0IGdsYW5jZSwgdGhlIHZhcmlhYmxlcyBhcHBlYXIgYXMgZXhwZWN0ZWQuIFBvaW50cyBvZiBpbnRlcmVzdDogDQoqIExhcmdlLCBwb3NpdGl2ZSBza2V3IGluIGB0aW1lX3NwZW5kX2NvbXBhbnlgLCBvbmUgb2Ygb3VyIGRpc2NyZXRlIHZhcmlhYmxlcw0KKiBMYXJnZSBTRCBpbiBgbnVtYmVyX3Byb2plY3RgIGFuZCBgYXZlcmFnZV9tb250aGx5X2hvdXJzYA0KKiBMYXJnZSBSYW5nZSBpbiBgYXZlcmFnZV9tb250aGx5X2hvdXJzIGANCg0KYGBge3IgUGVyY2VudCBBdHRyaXRpb259DQojIEFUVFJJVElPTiBQRVJDRU5UQUdFIElOIERBVEFTRVQNCkRhdGEgJT4lDQogIHRhYnlsKGxlZnQpICU+JSANCiAgYWRvcm5fcGN0X2Zvcm1hdHRpbmcoZGlnaXRzID0gMCwgYWZmaXhfc2lnbiA9IFRSVUUpDQpgYGANCg0KVGFraW5nIGEgbG9vayBhdCBvdXIgb3V0Y29tZSB2YXJpYWJsZSB3ZSBmaW5kIHRoYXQgT3VyIGRhdGEgaXMgdW5iYWxhbmNlZCwgZ2l2ZW4gYWJvdXQgMS80IG9mIHRoZSBvYnNlcnZhdGlvbnMgbGVmdCB0aGUgb3JnYW5pemF0aW9uIGFuZCAzLzQncyBzdGF5ZWQuIFdlJ2xsIG5lZWQgdG8gYWNjb3VudCBmb3IgdGhpcyBpbiBvdXIgcmVjaXBlcy4gDQoNCmBgYHtyIFZhcmlhYmxlIFN1bW1hcmllc30NCiNTRVZFUkFMIFRBQkxFUyBUTyBMT09LIEFUIFZBUklBQkxFUyBJTiBJU09MQVRJT04NCg0KIyBDT05USU5VT1VTIFZBUklBQkxFUw0KIyB0YWJ5bChEYXRhLCBzYXRpc2ZhY3Rpb25fbGV2ZWwpDQojIHRhYnlsKERhdGEsIGxhc3RfZXZhbHVhdGlvbikNCiMgdGFieWwoRGF0YSwgYXZlcmFnZV9tb250aGx5X2hvdXJzKQ0KDQojRElTQ1JFVEUgJiBDQVRFR09SSUNBTCBWQVJJQUJMRVMNCnRhYnlsKERhdGEsIG51bWJlcl9wcm9qZWN0KSAlPiUgDQogIGFkb3JuX3BjdF9mb3JtYXR0aW5nKGRpZ2l0cyA9IDAsIGFmZml4X3NpZ24gPSBUUlVFKQ0KdGFieWwoRGF0YSwgdGltZV9zcGVuZF9jb21wYW55KSAlPiUgDQogIGFkb3JuX3BjdF9mb3JtYXR0aW5nKGRpZ2l0cyA9IDAsIGFmZml4X3NpZ24gPSBUUlVFKQ0KdGFieWwoRGF0YSwgV29ya19hY2NpZGVudCkgJT4lIA0KICBhZG9ybl9wY3RfZm9ybWF0dGluZyhkaWdpdHMgPSAwLCBhZmZpeF9zaWduID0gVFJVRSkNCnRhYnlsKERhdGEsIHByb21vdGlvbl9sYXN0XzV5ZWFycykgJT4lIA0KICBhZG9ybl9wY3RfZm9ybWF0dGluZyhkaWdpdHMgPSAwLCBhZmZpeF9zaWduID0gVFJVRSkNCnRhYnlsKERhdGEsIGRlcGFydG1lbnQpICU+JSANCiAgYWRvcm5fcGN0X2Zvcm1hdHRpbmcoZGlnaXRzID0gMCwgYWZmaXhfc2lnbiA9IFRSVUUpDQp0YWJ5bChEYXRhLCBzYWxhcnkpICU+JSANCiAgYWRvcm5fcGN0X2Zvcm1hdHRpbmcoZGlnaXRzID0gMCwgYWZmaXhfc2lnbiA9IFRSVUUpDQpgYGANCg0KQ2hlY2tpbmcgdGhlIGJhbGFuY2Ugb2YgcHJlZGljdG9yIHZhcmlhYmxlcywgd2Ugc2VlOg0KDQoqIFRoZXJlIGlzIGEgbG9uZyB0YWlsIG9uIGB0aW1lX3NwZW50X2NvbXBhbnlgDQoqIE9ubHkgMTUlIG9mIG9ic2VydmF0aW9ucyBleHBlcmllbmNlZCAgYHdvcmtfYWNjaWRlbnRgIA0KKiBPbmx5IDIlIG9mIG9ic2VydmF0aW9ucyBoYXZlIGJlZW4gYHByb21vdGlvbl9sYXN0XzV5ZWFyc2ANCiogVGhlIGBkZXBhcnRtZW50YCBkaXN0cmlidXRpb24gaXMgdW5ldmVuLCB3aXRoIDI4JSBvZiBvYnNlcnZhdGlvbnMgc2hvd2luZyBpbiB0aGUgU2FsZXMgZGVwYXJ0bWVudC4gU3VwcG9ydCBhbmQgVGVjaG5pY2FsIGRlcGFydG1lbnRzIGNsb3NlbHkgZm9sbG93LiBPdGhlciBkZXBhcnRtZW50cyBhcmUgb25seSA1JSBvciA2JSBvZiB0aGUgZGF0YS4gDQoNCg0KIyMgRGF0YSBWaXN1YWxpemF0aW9uDQoNCmBgYHtyIEludHJvIFRhYmxlfQ0KRGF0YSAlPiUgDQogIGludHJvZHVjZSgpICU+JSANCiAgcGl2b3RfbG9uZ2VyKGV2ZXJ5dGhpbmcoKSkNCmBgYA0KDQpgYGB7ciBJbnRybyBWaXp9DQpwbG90X2ludHJvKERhdGEpDQpgYGANCg0KYGBge3IgRnJlcXVlbmN5fQ0KI2xpYnJhcnkoZnVuTW9kZWxpbmcpDQoNCiNGQUNUT1IgVkFSSUFCTEUgRlJFUVVFTkNZDQpmcmVxKERhdGEpDQpgYGANCg0KKiBUaGUgaGlnaGVzdCBwZXJjZW50YWdlIG9mIHBlb3BsZSBhcmUgZm91bmQgaW4gU2FsZXMuIA0KKiBMb3cgYW5kIE1lZGl1bSBzYWxhcnkgcmFuZ2UgYXJlIG1vc3QgcHJldmFsZW50LCB3aXRoIEhpZ2ggc2FsYXJ5IHJhbmdlIGFjY291bnRpbmcgZm9yIG9ubHkgOCUgb2Ygb2JzZXJ2YXRpb25zLg0KDQpgYGB7ciAgU2tpbSAtIE51bWVyaWN9DQpza2ltKERhdGEpICU+JSANCiAgZmlsdGVyKHNraW1fdHlwZSA9PSAnbnVtZXJpYycpDQpgYGANCg0KDQpgYGB7ciBWaXogLSBDb250aW51b3VzICYgRGlzY3JldGV9DQojcGxvdF9udW0oRGF0YSkNCg0KIyBMZXQncyBsb29rIGF0IHRoaXMgd2l0aG91dCBvdXIgZHVtbXkgY29kZWQgZGF0YQ0KRGF0YSAlPiUgDQogICBzZWxlY3QoInNhdGlzZmFjdGlvbl9sZXZlbCIsIA0KICAgICAgICAgICJsYXN0X2V2YWx1YXRpb24iLCANCiAgICAgICAgICAibnVtYmVyX3Byb2plY3QiLCAgICAgICANCiAgICAgICAgICAiYXZlcmFnZV9tb250aGx5X2hvdXJzIiwgDQogICAgICAgICAgInRpbWVfc3BlbmRfY29tcGFueSIpICU+JSANCiAgcGxvdF9udW0oKQ0KYGBgDQoNCipPdXIgbnVtZXJpYyB2YXJpYWJsZXMgYXJlIG5vdCB2ZXJ5IG5vcm1hbGx5IGRpc3RyaWJ1dGVkLiBBdmVyYWdlIG1vbnRobHkgIGFuZCBsYXN0IGV2YWx1YXRpb24gYXBwZWFyIGJpbW9kYWwuVGhlIHJlbWFpbmluZyB2YXJpYWJsZXMgYXJlIHNvbWV3aGF0IHNrZXdlZC4gDQoNCg0KYGBge3IgVml6IC0gRHVtbXl9DQojIERVTU1ZIENPREVEDQpEYXRhICU+JSANCiAgIHNlbGVjdCgiV29ya19hY2NpZGVudCIsIA0KICAgICAgICAgICJsZWZ0IiwgDQogICAgICAgICAgInByb21vdGlvbl9sYXN0XzV5ZWFycyIpICU+JSANCiAgcGxvdF9udW0oKQ0KYGBgDQpPdXIgY2F0ZWdvcnkgdmFyaWFibGVzIGFyZSB1bmJhbGFuY2VkLg0KDQoqIFRoZXJlIGFyZSB2ZXJ5IGZldyBwcm9tb3Rpb25zIGluIHRoZSBwYXN0IDUgeWVhcnMuDQoqIFRoZXJlIGFyZSByZWxhdGl2ZWx5IGZldyBvYnNlcnZhdGlvbnMgaGF2aW5nIFdvcmsgQWNjaWRlbnRzLg0KDQpgYGB7ciBGcmVxIC0gQWxsICYgQXR0cml0IE9ubHl9DQojIyBMZWZ0OiBmcmVxdWVuY3kgZGlzdHJpYnV0aW9uIG9mIGFsbCBkaXNjcmV0ZSB2YXJpYWJsZXMNCnBsb3RfYmFyKERhdGEpDQoNCiMjIFJpZ2h0OiBgbGVmdGAgZGlzdHJpYnV0aW9uIG9mIGFsbCBkaXNjcmV0ZSB2YXJpYWJsZXMNCnBsb3RfYmFyKERhdGEsIHdpdGggPSAibGVmdCIpDQpgYGANCg0KKiBUaGVyZSBhcmUgbm8gYmlnIGRpZmZlcmVuY2VzIGluIGRpc3RyaWJ1dGlvbiB3aGVuIGFjY291bnRpbmcgZm9yIGBsZWZ0YC4gU29tZSBvZiB0aGUgZGVwYXJ0bWVudHMgd2l0aCBsZXNzIHJlcHJlc2VudGF0aW9uIGhhdmUgbW9yZSBhdHRyaXRpb24sIHN1Y2ggYXMgSFIgYW5kIFJhbmRELg0KDQoNCmBgYHtyIEJhciAtIExlZnR9DQpwbG90X2JhcihEYXRhLCBieSA9ICJsZWZ0IikNCmBgYA0KDQpgYGB7ciBCYXIgLSBTYWxhcnl9DQpwbG90X2JhcihEYXRhLCBieSA9ICJzYWxhcnkiKQ0KYGBgDQoNCmBgYHtyIEhpc3QgLSBOdW1lcmljfQ0KRGF0YSAlPiUgDQogIHNlbGVjdCgic2F0aXNmYWN0aW9uX2xldmVsIiwgDQogICAgICAgICAibGFzdF9ldmFsdWF0aW9uIiwgDQogICAgICAgICAibnVtYmVyX3Byb2plY3QiLCAgICAgICANCiAgICAgICAgICJhdmVyYWdlX21vbnRobHlfaG91cnMiLCANCiAgICAgICAgICJ0aW1lX3NwZW5kX2NvbXBhbnkiKSAlPiUgDQogIHBsb3RfaGlzdG9ncmFtKCkNCiAgDQpgYGANCg0KDQpgYGB7ciBEZW5zaXR5IC0gTnVtZXJpY30NCkRhdGEgJT4lIA0KICBzZWxlY3QoImF2ZXJhZ2VfbW9udGhseV9ob3VycyIsIA0KICAgICAgICAgImxhc3RfZXZhbHVhdGlvbiIsIA0KICAgICAgICAgInNhdGlzZmFjdGlvbl9sZXZlbCIpICU+JSANCiAgcGxvdF9kZW5zaXR5KCkNCmBgYA0KDQpgYGB7ciBRUSBQbG90c30NCiNRUSBQTE9UUw0KRGF0YSAlPiUgDQogIHNlbGVjdCgiYXZlcmFnZV9tb250aGx5X2hvdXJzIiwgDQogICAgICAgICAibGFzdF9ldmFsdWF0aW9uIiwgDQogICAgICAgICAic2F0aXNmYWN0aW9uX2xldmVsIikgJT4lIA0KICAgIHBsb3RfcXEoKQ0KYGBgDQoqIFdlIHNlZSB0aGUgZ2VuZXJhbCBzaGFwZSBvZiByZXNpZHVhbHMgdGhhdCB3ZSBzZWVrIChzLWN1cnZlKS4NCg0KIyMgQ2hlY2sgZm9yIFZhcmlhbmNlDQoNCmBgYHtyIENyZWF0ZSBPYmplY3QgLSBMZWZ0fQ0KbGVmdCA8LSBEYXRhJGxlZnQgI0NyZWF0ZSB2YXJpYWJsZQ0KYGBgDQoNCg0KYGBge3IgU2NhdHRlcnBsb3RzfQ0KRGF0YSAlPiUgDQogIHBsb3RfcHJjb21wKG1heGNhdCA9IDVMKQ0KYGBgDQoNCg0KYGBge3IgVmFyfQ0KdmFyKGxlZnQpDQpgYGANCiMjIERhdGEgU3VtbWFyeSANCg0KQXMgd2Ugc2VlIGluIHRoZSBEYXRhOg0KDQpPYnNlcnZhdGlvbnM6IDksOTk5IHdpdGggVmFyaWFibGVzOiAxMA0KDQpUaGVyZSBpcyBubyBtaXNzaW5nIGRhdGEgb3IgZHVwbGljYXRlIGRhdGEuDQoNClRoZSBPdXRjb21lIHZhcmlhYmxlIGlzIGBsZWZ0YCB3aXRoIDc2MzUgKDc2JSkgJ05vJyBhbmQgMjM2NCAoMjQlKSAnWWVzJy4gVGhpcyB2YXJpYWJsZSBpbmRpY2F0ZXMgaWYgYW4gZW1wbG95ZWUgbGVmdCB0aGUgY29tcGFueS4gDQoNClRocmVlIG9mIG91ciB2YXJpYWJsZXMgYXJlIGR1bW15IGNvZGVkICgwPU5vLzE9WWVzKSwgaW5jbHVkaW5nIGBXb3JrX2FjY2lkZW50YCwgYGxlZnRgLCBgcHJvbW90aW9uX2xhc3RfNXllYXJzYC4gT25lIG9mIHRoZXNlIHZhcmlhYmxlcywgYExlZnRgLCBpcyBvdXIgb3V0Y29tZSB2YXJpYWJsZSwgZGV0ZXJtaW5pbmcgaWYgc29tZW9uZSBzdGF5ZWQgYXQgb3IgbGVmdCB0aGUgY29tcGFueS4gDQoNClRocmVlIG9mIG91ciBudW1lcmljIHZhcmlhYmxlcyBhcHBlYXIgdG8gYmUgY29udGludW91cywgaW5jbHVkaW5nIGBhdmVyYWdlX21vbnRobHlfaG91cnNgLCBgc2F0aXNmYWN0aW9uX2xldmVsYCwgYW5kIGBsYXN0X2V2YWx1YXRpb25gLiANCg0KVHdvIG51bWVyaWMgdmFyaWFibGVzIGFyZSBkaXNjcmV0ZSwgaW5jbHVkaW5nIG9ubHkgNiAoYG51bWJlcl9wcm9qZWN0YCkgYW5kIDggKGB0aW1lX3NwZW5kX2NvbXBhbnlgKSB2YWx1ZXMgZWFjaC4NCg0KRGVtb2dyYXBoaWMgZGF0YSBpcyBsaW1pdGVkLiBWYXJpYWJsZXMgc3VjaCBhcyBgZGVwYXJ0bWVudGAsIGBzYWxhcnlgIGxldmVsLCBhbmQgYHRpbWVfc3BlbmRfY29tcGFueWAgKHRlbnVyZSBpbiB5ZWFycykgYXJlIHRoZSBjbG9zZXN0IHRoaW5nIHRvIGRlbW9ncmFwaGljIGluZm9ybWF0aW9uIHdlIGhhdmUuIA0KDQpTb21lIHZhcmlhYmxlcyBwcm92aWRlIHBlcnNvbmFsIGluZm9ybWF0aW9uLCBzdWNoIGFzIGBzYXRpc2ZhY3Rpb25fbGV2ZWxgIGFuZCBgV29ya19hY2NpZGVudGAuIEFuZCBhIGZldyBpbmRpY2F0ZSBvdmVyYWxsIHBlcmZvcm1hbmNlIGZvciB0aGUgZW1wbG95ZWUsIHN1Y2ggYXMgYGxhc3RfZXZhbHVhdGlvbmAgYW5kIGBwcm9tb3Rpb25fbGFzdF81eWVhcnNgLiANCg0KV2UnbGwgbmVlZCB0byBhY2NvdW50IGZvciB1bmJhbGFuY2UgaW4gdGhlIGRhdGFzZXQgaW4gcHJlcHJvY2Vzc2luZy4gV2UnbGwgYWxzbyBuZWVkIHRvIG5vcm1hbGl6ZSAoY2VudGVyIGFuZCBzY2FsZSkgbnVtZXJpYyBwcmVkaWN0b3JzLiANCg0KDQoNCiMgRGF0YSBFeHBsb3JhdGlvbiANCg0KIyMgQ29ycmVsYXRpb25zDQoNCmBgYHtyIENvcnJlbGF0aW9uIE1hdHJpeH0NCmxpYnJhcnkoY29ycnIpDQoNCkRhdGEgJT4lIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lIA0KICBjb3JyZWxhdGUoKQ0KYGBgDQoNCmBgYHtyIENvcnIgT2JqZWN0fQ0KI0NSRUFURSBPQkpFQ1QgT0YgQ09SUkVMQVRJT04gTUFUUklYICANCmNvcl9tYXRyaXggPC0gRGF0YSAlPiUgDQogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lIA0KICBjb3JyZWxhdGUoKQ0KYGBgDQoNCmBgYHtyIENvciBNYXRyaXggLSBSZWFycmFuZ2V9DQojUkVBUlJBTkdFIFNIT1VMRCBHUk9VUCBBTlkgSElHSExZIENPUlJFTEFURUQgQ09MVU1OUw0KY29yX21hdHJpeCAlPiUgDQogICByZWFycmFuZ2UoKQ0KYGBgDQoNCmBgYHtyIENvcnJlbGF0aW9uIE1hdHJpeCAtIE91dGNvbWUgZm9jdXN9DQpjb3JfbWF0cml4ICU+JSANCiAgY29ycnI6OmZvY3VzKGxlZnQpICU+JSANCiAgYXJyYW5nZShkZXNjKGxlZnQpKQ0KYGBgDQoNCg0KYGBge3IgQ29ycmVsYXRpb24gTWF0cml4IC0gdyBQcm9qZWN0IGZvY3VzfQ0KY29yX21hdHJpeCAlPiUgDQogIGNvcnJyOjpmb2N1cyhsZWZ0LCBudW1iZXJfcHJvamVjdCwgc2F0aXNmYWN0aW9uX2xldmVsKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhsZWZ0KSkNCmBgYA0KDQoNCg0KYGBge3IgQ29yIE1hdHJpeCAtIFN0cmV0Y2h9DQpjb3JfbWF0cml4ICU+JSANCiAgc3RyZXRjaChuYS5ybSA9IFRSVUUsIHJlbW92ZS5kdXBzID0gVFJVRSkNCg0KI1RvIGNyZWF0ZSBhIHN0cnVjdHVyZWQgdmVyc2lvbiBvZiBvdXIgY29ycmVsYXRpb24gZGF0YSwgd2hpY2ggd2UgY2FuIHBhc3MgdG8gZ2dwbG90IG9yIGRwbHlyIGZvciBmdXJ0aGVyIGRhdGEgYW5hbHlzaXMsIHdlIGNhbiB1c2UgdGhlIHN0cmV0Y2goKSBmdW5jdGlvbi4gaHR0cHM6Ly93d3cuZ211ZGF0YW1pbmluZy5jb20vbGVzc29uLTA3LXItdHV0b3JpYWwuaHRtbA0KYGBgDQoNCg0KYGBge3IgQ29yIE1hdHJpeCAtIEhpc3R9DQojIGhpc3RvZ3JhbSBvZiB0aGUgY29ycmVsYXRpb24gdmFsdWVzIG9ic2VydmVkIGZvciBhbGwgdW5pcXVlIHBhaXJzIG9mIHZhcmlhYmxlcyBpbiBjb3JfbWF0cml4DQoNCmNvcl9tYXRyaXggJT4lIA0KICBzdHJldGNoKG5hLnJtID0gVFJVRSwgcmVtb3ZlLmR1cHMgPSBUUlVFKSAlPiUgDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSByKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gJyMwMDZFQTEnLCBjb2xvciA9ICd3aGl0ZScsIGJpbnMgPSAxNSkgKw0KICBsYWJzKHRpdGxlID0gJ0NvcnJlbGF0aW9uIFZhbHVlcycsDQogICAgICAgeCA9ICdDb3JyZWxhdGlvbiBWYWx1ZScsDQogICAgICAgeSA9ICdDb3VudCcpDQpgYGANCg0KDQpgYGB7ciBDb3IgTWF0cml4IGFzIE1hdHJpeH0NCmNvcl9tYXRyaXgyIDwtIERhdGEgJT4lIA0KICAgICAgICAgICAgICAgICAgIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lIA0KICAgICAgICAgICAgICAgICAgIGNvcigpDQoNCmNvcl9tYXRyaXgyDQpgYGANCg0KYGBge3IgQ29yIE1hdHJpeCBieSBGYWN0b3IgTG9hZGluZ3N9DQpjb3JycGxvdChjb3JfbWF0cml4MiwgDQogICAgICAgICBtZXRob2QgPSAiZWxsaXBzZSIsICMgZWxsaXBzZXMgaW5zdGVhZCBvZiBjaXJjbGVzDQogICAgICAgICB0eXBlID0gInVwcGVyIiwgIyBrZWVwIHVwcGVyIGhhbGYgb25seQ0KICAgICAgICAgb3JkZXIgPSAiRlBDIiwgIyBvcmRlciBieSBsb2FkaW5ncyBvbiBGUEMNCiAgICAgICAgIHRsLmNvbCA9ICJibGFjayIpICMgY29sb3Igb2YgdmFyaWFibGUgdGV4dCBsYWJsZXMNCmBgYA0KDQojIyMgQ29ycmVsYXRpb24gU3VtbWFyeQ0KDQpUaGVyZSBhcmUgbm8gdmFyaWFibGVzIHRoYXQgYXJlIG92ZXJseSBjb3JyZWxhdGVkIHRoYXQgbmVlZCB0byBiZSByZW1vdmVkLiBDb2xsaW5lYXJpdHkgc2hvdWxkIG5vdCBiZSBhbiBpc3N1ZS4gUmVsYXRpb25zaGlwcyBpbiB2YXJpYWJsZXMgc2VlbXMgc2Vuc2ljYWwuIEZvciBleGFtcGxlIC0gTnVtYmVyIG9mIFByb2plY3RzLCBBdmVyYWdlIE1vbnRobHkgSG91cnMsIGFuZCBMYXN0IEV2YWx1YXRpb24gYWxsIGNvcnJlbGF0ZSBtb3JlIHN0cm9uZ2x5IHdpdGggb25lIGFub3RoZXIsIHdoaWNoIG1ha2VzIHNlbnNlIGluIGEgd29yayBlbnZpcm9ubWVudC4gTG93ZXIgY29ycmVsYXRpb25zIGFyZSBzZWVuIGJldHdlZW4gc2F0aXNmYWN0aW9uIGxldmVsIGFuZCAibGVmdCIsIHdoaWNoIHdlIHdvdWxkIGFsc28gZXhwZWN0LiANCg0KIyMgQ3Jvc3MtVGFicw0KDQpgYGB7ciBNZWFucyBieSBBdHRyaXRpb259DQojQ1JFQVRFIEEgVEFCTEUgT0YgTUVBTlMgRk9SIE5VTUVSSUMgVkFSSUFCTEUsIEdST1VQRUQgQlkgT1VUQ09NRSBWQVJJQUJMRQ0KRGF0YSAlPiUNCiAgZ3JvdXBfYnkobGVmdCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICAgICAgICBjb3VudCA9IG4oKSwNCiAgICAgICAgICBtZWFuX3NhdCA9IG1lYW4oc2F0aXNmYWN0aW9uX2xldmVsKSwNCiAgICAgICAgICBtZWFuX2V2YWwgPSBtZWFuKGxhc3RfZXZhbHVhdGlvbiksDQogICAgICAgICAgbWVhbl9wcm9qID0gbWVhbihudW1iZXJfcHJvamVjdCksDQogICAgICAgICAgbWVhbl9ocnMgPSBtZWFuKGF2ZXJhZ2VfbW9udGhseV9ob3VycyksDQogICAgICAgICAgbWVhbl95cnMgPSBtZWFuKHRpbWVfc3BlbmRfY29tcGFueSkNCiAgICAgICAgICAgICkNCmBgYA0KDQpHcm91cGluZyBieSBvdXIgb3V0Y29tZSB2YXJpYWJsZSwgYGxlZnRgLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIG1lYW4gZm9yIG1hbnkgb2Ygb3VyIHByZWRpY3RvciB2YXJpYWJsZXMgZG9lcyBub3QgdmFyeSBieSBtdWNoLiBUaGUgbGFyZ2VzdCBkaWZmZXJlbmNlIGlzIGZvdW5kIGluIGBzYXRpc2ZhY3Rpb25fbGV2ZWxgLCB3aXRoIHRob3NlIHdobyBsZWZ0IHNob3dpbmcgYSBsb3dlciBzYXRpc2ZhY3Rpb24gbGV2ZWwsIG9uIGF2ZXJhZ2UuDQoNCg0KYGBge3IgQ1QgLSBTYXQgJiBTYWxhcnl9DQojU0FUSVNGQUNUSU9OIEJZIFNBTEFSWSBMRVZFTA0KRGF0YSAlPiUNCmdyb3VwX2J5KHNhbGFyeSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBuID0gbigpLA0KICAgIG1lYW4gPSBtZWFuKHNhdGlzZmFjdGlvbl9sZXZlbCksDQogICAgc2QgPSBzZChzYXRpc2ZhY3Rpb25fbGV2ZWwpDQogICkNCmBgYA0KDQpBdmVyYWdlIFNhdGlzZmFjdGlvbiBsZXZlbCBkb2VzIG5vdCB2YXJ5IG11Y2ggYnkgc2FsYXJ5IGxldmVsLCBhbHRob3VnaCBpdCBkb2VzIGdldCBzbGlnaHRseSBoaWdoZXIgYXQgZWFjaCBsZXZlbC4NCg0KYGBge3IgQ1QgLSBTYWxhcnl9DQojVU5ERVJTVEFORElORyBDT01CSU5BVElPTlMgT0YgREFUQSANCkRhdGEgJT4lDQogIHRhYnlsKGxlZnQsIHNhbGFyeSkgJT4lIA0KICBhZG9ybl90b3RhbHMoYygncm93JywgJ2NvbCcpKSAlPiUNCiAgYWRvcm5fcGVyY2VudGFnZXMoJ2NvbCcpICU+JSANCiAgYWRvcm5fcGN0X2Zvcm1hdHRpbmcoZGlnaXRzID0gMCkgJT4lDQogIGFkb3JuX25zKCkgJT4lDQogIGFkb3JuX3RpdGxlKCdjb21iaW5lZCcpDQpgYGANCg0KKiBEZXBhcnR1cmVzIGJ5IHNhbGFyeSBsZXZlbCBmb2xsb3dzIHRoZSBvdmVyYWxsIGRhdGFzZXQgYmFsYW5jZSBvZiBhcHByb3hpbWF0ZWx5IDc1JSBzdGF5IC8gMjUlIGxlYXZlIGF0IExvdyBhbmQgTWVkaXVtIGxldmVscy4NCg0KKiBFeGNlcHRpb246IEZvciB0aG9zZSB3aXRoIGhpZ2ggc2FsYXJ5LCBvbmx5IDglIGxlZnQgdGhlIGNvbXBhbnkuDQogDQoNCmBgYHtyIENUIC0gRGVwdH0NCkRhdGEgJT4lDQogIHRhYnlsKGxlZnQsIGRlcGFydG1lbnQpICAlPiUgDQogIGFkb3JuX3RvdGFscyhjKCdyb3cnLCAnY29sJykpICU+JQ0KICBhZG9ybl9wZXJjZW50YWdlcygnY29sJykgJT4lIA0KICBhZG9ybl9wY3RfZm9ybWF0dGluZyhkaWdpdHMgPSAwKSAlPiUNCiAgYWRvcm5fbnMoKSAlPiUNCiAgYWRvcm5fdGl0bGUoJ2NvbWJpbmVkJykNCmBgYA0KDQoqIFdlIHNlZSBhIHJlbGF0aXZlbHkgZXZlbiBzcGxpdCBvZiBkZXBhcnR1cmVzIGluIGVhY2ggZGVwYXJ0bWVudCwgdGVuZGluZyB0b3dhcmQgNzUlIHN0YXkgLyAyNSUgZ28sIHdoaWNoIG1hdGNoZXMgb3VyIG92ZXJhbGwgZGF0YXNldCBiYWxhbmNlLiANCg0KKiBUaGUgb25seSBleGNlcHRpb24gaXMgTWFuYWdlbWVudCAoODUlIHN0YXkpLiAoQWxzbywgb3VyIFJhbmRvbSBWYXJpYWJsZSBoYXMgYW4gODUlIC8gMTUlIHNwbGl0KQ0KDQpgYGB7ciBDVCAtIE51bSBQcm9qc30NCkRhdGEgJT4lDQogIHRhYnlsKGxlZnQsIG51bWJlcl9wcm9qZWN0KSAlPiUgDQogIGFkb3JuX3RvdGFscyhjKCdyb3cnLCAnY29sJykpICU+JQ0KICBhZG9ybl9wZXJjZW50YWdlcygnY29sJykgJT4lIA0KICBhZG9ybl9wY3RfZm9ybWF0dGluZyhkaWdpdHMgPSAwKSAlPiUNCiAgYWRvcm5fbnMoKSAlPiUNCiAgYWRvcm5fdGl0bGUoJ2NvbWJpbmVkJykNCmBgYA0KDQoqIFRoZSByZWxhdGlvbnNoaXAgb2YgdGhlIGBudW1iZXJfcHJvamVjdGAgdmFyaWFibGUgdG8gb3V0Y29tZSB2YXJpYWJsZSB2YXJpZXMgZnJvbSBvdGhlcnMuIFRoZXJlIHNlZW1zIHRvIGJlIGEgInN3ZWV0IHNwb3QiIG9mIDMgb3IgNCBwcm9qZWN0cyBoYXZpbmcgbGVzcyBhdHRyaXRpb24uIFRob3NlIHdpdGggNyBwcm9qZWN0cyBhdHRyaXRlZCBhdCAxMDAlLiANCg0KYGBge3IgQ1QgLSBQcm9tb3Rpb259DQpEYXRhICU+JQ0KICB0YWJ5bChsZWZ0LCBwcm9tb3Rpb25fbGFzdF81eWVhcnMpICU+JSANCiAgYWRvcm5fdG90YWxzKGMoJ3JvdycsICdjb2wnKSkgJT4lDQogIGFkb3JuX3BlcmNlbnRhZ2VzKCdjb2wnKSAlPiUgDQogIGFkb3JuX3BjdF9mb3JtYXR0aW5nKGRpZ2l0cyA9IDApICU+JQ0KICBhZG9ybl9ucygpICU+JQ0KICBhZG9ybl90aXRsZSgnY29tYmluZWQnKQ0KYGBgDQoNCiogRm9yIHRob3NlIHdobyBoYXZlIG5vdCBiZWVuIHByb21vdGVkIGluIGxhc3QgNSB5ZWFycywgb3VyIGV4cGVjdGVkIGF0dHJpdGlvbiByYXRlIGhvbGRzLiBUaGVyZSBpcyBhIG11Y2ggaGlnaGVyIHJldGVudGlvbiByYXRlICg5NCUpIGZvciB0aG9zZSB3aG8gaGF2ZSBiZWVuIHByb21vdGVkLiANCg0KDQpgYGB7ciBDVCAtIEFjY2lkZW50fQ0KRGF0YSAlPiUNCiAgdGFieWwobGVmdCwgV29ya19hY2NpZGVudCkgJT4lIA0KICBhZG9ybl90b3RhbHMoYygncm93JywgJ2NvbCcpKSAlPiUNCiAgYWRvcm5fcGVyY2VudGFnZXMoJ2NvbCcpICU+JSANCiAgYWRvcm5fcGN0X2Zvcm1hdHRpbmcoZGlnaXRzID0gMCkgJT4lDQogIGFkb3JuX25zKCkgJT4lDQogIGFkb3JuX3RpdGxlKCdjb21iaW5lZCcpDQpgYGANCg0KKiBGb3IgdGhvc2Ugd2hvIGhhdmUgbm90IGhhZCBhIHdvcmsgYWNjaWRlbnQsIG91ciBleHBlY3RlZCBhdHRyaXRpb24gcmF0ZSBob2xkcy4gVGhlcmUgaXMgYSBtdWNoIGhpZ2hlciByZXRlbnRpb24gcmF0ZSAoOTMlKSBmb3IgdGhvc2Ugd2hvIGhhdmUgYmVlbiBpbiBhbiBhY2NpZGVudC4gDQoNCmBgYHtyIENUIC0gVGVudXJlfQ0KRGF0YSAlPiUNCiAgdGFieWwobGVmdCwgdGltZV9zcGVuZF9jb21wYW55KSAlPiUgDQogIGFkb3JuX3RvdGFscyhjKCdyb3cnLCAnY29sJykpICU+JQ0KICBhZG9ybl9wZXJjZW50YWdlcygnY29sJykgJT4lIA0KICBhZG9ybl9wY3RfZm9ybWF0dGluZyhkaWdpdHMgPSAwKSAlPiUNCiAgYWRvcm5fbnMoKSAlPiUNCiAgYWRvcm5fdGl0bGUoJ2NvbWJpbmVkJykNCmBgYA0KDQoqIFRoZXJlIGlzIDEwMCUgcmV0ZW50aW9uIGZvciBlbXBsb3llZXMgd2hvIGhhdmUgc3RheWVkIHdpdGggdGhlIGNvbXBhbnkgZm9yIDcgb3IgbW9yZSB5ZWFycy4NCg0KYGBge3IgQ1QgLSBMZWZ0ICYgU2F0IExldmVsfQ0KDQpEYXRhICU+JQ0KZ3JvdXBfYnkobGVmdCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBuID0gbigpLA0KICAgIG1lYW4gPSBtZWFuKHNhdGlzZmFjdGlvbl9sZXZlbCksDQogICAgc2QgPSBzZChzYXRpc2ZhY3Rpb25fbGV2ZWwpDQogICkNCmBgYA0KKiBBdmVyYWdlIFNhdGlzZmFjdGlvbiBsZXZlbCBpcyBtdWNoIGxvd2VyIGZvciBwZW9wbGUgd2hvIGxlZnQgdGhlIGNvbXBhbnkuDQoNCmBgYHtyIENUIC0gTGVmdCAmIEV2YWx9DQoNCkRhdGEgJT4lDQpncm91cF9ieShsZWZ0KSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIG4gPSBuKCksDQogICAgbWVhbiA9IG1lYW4obGFzdF9ldmFsdWF0aW9uKSwNCiAgICBzZCA9IHNkKGxhc3RfZXZhbHVhdGlvbikNCiAgKQ0KYGBgDQoqIEF2ZXJhZ2Ugc2NvcmUgb24gdGhlIGxhc3QgZXZhbHVhdGlvbiBpcyBhcHByb3hpbWF0ZWx5IGV2ZW4gZm9yIHRob3NlIHdobyBsZWZ0IHRoZSBjb21wYW55Lg0KDQpgYGB7ciBDVCAtIExlZnQgJiBIb3Vyc30NCg0KRGF0YSAlPiUNCmdyb3VwX2J5KGxlZnQpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbiA9IG4oKSwNCiAgICBtZWFuID0gbWVhbihhdmVyYWdlX21vbnRobHlfaG91cnMpLA0KICAgIHNkID0gc2QoYXZlcmFnZV9tb250aGx5X2hvdXJzKQ0KICApDQpgYGANCiogQXZlcmFnZSBtb250aGx5IGhvdXJzIGlzIHNsaWdodGx5IGhpZ2hlciBmb3IgdGhvc2Ugd2hvIGxlZnQgdGhlIGNvbXBhbnkuIA0KDQojIyBDcm9zc3RhYiBWaXN1YWxpemF0aW9ucw0KDQpBZnRlciBleGFtaW5pbmcgdGhlIGNyb3NzdGFiIHRhYmxlcyBvZiB2YXJpb3VzIGNvbWJpbmF0aW9ucyBvZiBvdXIgZGF0YSwgd2UnbGwgYWxzbyBydW4gc29tZSB2aXN1YWxpemF0aW9ucyB0byBzZWUgaWYgYW55dGhpbmcgZWxzZSBtYXkgcG9wIG91dCB0aGF0IGlzIHN1cnByaXNpbmcgb3IgaW50ZXJlc3RpbmcgZm9yIG91ciBtb2RlbGxpbmcgcHVycG9zZXMuDQoNCmBgYHtyIENUIC0gVml6fQ0KDQojbGlicmFyeShDR1BmdW5jdGlvbnMpIA0KI2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9Qm9BdkF1T1l2M3MNCg0KI1ZJU1VBTElaSU5HDQoNClBsb3RYVGFiczIoRGF0YSwgZGVwYXJ0bWVudCwgdGltZV9zcGVuZF9jb21wYW55LCByZXN1bHRzLnN1YnRpdGxlID0gRkFMU0UpDQpQbG90WFRhYnMyKERhdGEsIGRlcGFydG1lbnQsIG51bWJlcl9wcm9qZWN0LCByZXN1bHRzLnN1YnRpdGxlID0gRkFMU0UpDQpQbG90WFRhYnMyKERhdGEsIGRlcGFydG1lbnQsIHRpbWVfc3BlbmRfY29tcGFueSwgcmVzdWx0cy5zdWJ0aXRsZSA9IEZBTFNFKQ0KDQpQbG90WFRhYnMoRGF0YSwgZGVwYXJ0bWVudCwgc2FsYXJ5KQ0KUGxvdFhUYWJzKERhdGEsIG51bWJlcl9wcm9qZWN0LCBzYWxhcnkpDQoNClBsb3RYVGFicyhEYXRhLCBsZWZ0LCBkZXBhcnRtZW50KQ0KUGxvdFhUYWJzKERhdGEsIGxlZnQsIHNhbGFyeSkNClBsb3RYVGFicyhEYXRhLCBsZWZ0LCBudW1iZXJfcHJvamVjdCkNClBsb3RYVGFicyhEYXRhLCBsZWZ0LCB0aW1lX3NwZW5kX2NvbXBhbnkpDQoNCmBgYA0KDQojIyBDcm9zc3RhYiBPYnNlcnZhdGlvbnMgU3VtbWFyeQ0KDQpJbiB2aWV3aW5nIHZhcmlvdXMgY29tYmluYXRpb25zIG9mIG91ciB2YXJpYWJsZXMsIHRoZSByZWxhdGlvbnNoaXBzIHNlZW0gdG8gbWFrZSBzZW5zZSBmb3IgdGhlIGRhdGEgc2V0IGV4cGxvcmVkLiBGb3IgZXhhbXBsZSwgYXZlcmFnZSBzYXRpc2ZhY3Rpb24gbGV2ZWwgZGVjcmVhc2VzIGZvciB0aG9zZSB3aG8gbGVmdCB0aGUgY29tcGFueSBhbmQgaW5jcmVhc2VzIHNsaWdodGx5IGZvciBlYWNoIGxldmVsIG9mIHNhbGFyeS4gQXZlcmFnZSBtb250aGx5IGhvdXJzIGlzIHNsaWdodGx5IGhpZ2hlciBmb3IgcGVvcGxlIHdobyBsZWZ0IHRoZSBjb21wYW55Lg0KDQpPbiB0aGUgd2hvbGUsIG91ciBkYXRhc2V0IGhhcyBhbiBhcHByb3hpbWF0ZWx5IDc1LzI1IHNwbGl0IG9mIHJldGVudGlvbiBhbmQgYXR0cml0aW9uLiBJbiBtb3N0IGNhc2VzLCB0aGlzIGF0dHJpdGlvbiBwZXJjZW50YWdlIHNwbGl0IHN0YXllZCBjb25zdGFudCB3aGVuIGFjY291bnRpbmcgZm9yIHZhcmlvdXMgZmFjZXRzIG9mIHRoZSBkYXRhLiBBIGZldyBleGNlcHRpb25zIHRvIHRoZSB+MjUlIGF0dHJpdGlvbiByYXRlIHdlcmUgbm90ZWQ6IA0KDQoqIFdoZW4gYHNhbGFyeWAgaXMgSGlnaCA9ICA4JSBhdHRyaXRpb24NCiogV2hlbiBgZGVwYXJ0bWVudGAgaXMgTWFuYWdlbWVudCA9ICAxNSUgYXR0cml0aW9uIA0KKiBXaGVuIGBudW1iZXJfcHJvamVjdHNgIGlzLi4uDQogLS0gNyBwcm9qZWN0cyA9IDEwMCUgYXR0cml0aW9uIChoaWdoKQ0KIC0tIDYgcHJvamVjdHMgPSA1NiUgYXR0cml0aW9uIChoaWdoKQ0KIC0tIDUgcHJvamVjdHMgPSAyMiUgYXR0cml0aW9uIChleHBlY3RlZCkNCiAtLSA0IHByb2plY3RzID0gOSUgYXR0cml0aW9uIChsb3cpDQogLS0gMyBwcm9qZWN0cyA9IDIlIGF0dHJpdGlvbiAobG93KQ0KIC0tIDIgcHJvamVjdHMgPSA2NSUgYXR0cml0aW9uIChoaWdoKQ0KKiBXaGVuIGBwcm9tb3RlZF9sYXN0XzV5cnNgIGlzIFllcyA9IDYlIGF0dHJpdGlvbg0KKiBXaGVuIGBXb3JrX2FjY2lkZW50YCBpcyBZZXMgPSA3JSBhdHRyaXRpb24gDQoqIFdoZW4gYHRpbWVfc3BlbmRfY29tcGFueWAgaXMgPjcgeWVhcnMgPSAwJSBhdHRyaXRpb24gDQoNCg0KIyBTcGxpdHRpbmcgRGF0YSBmb3IgTW9kZWxpbmcNCg0KIyMgT3V0Y29tZSBDbGFzcyBJbWJhbGFuY2UNCg0KYGBge3IgSW1iYWxhbmNlfQ0KIyBBVFRSSVRJT04gUEVSQ0VOVEFHRSBJTiBEQVRBU0VUDQpEYXRhICU+JQ0KICB0YWJ5bChsZWZ0KSAlPiUgDQogIGFkb3JuX3BjdF9mb3JtYXR0aW5nKGRpZ2l0cyA9IDAsIGFmZml4X3NpZ24gPSBUUlVFKQ0KYGBgDQoNClRha2luZyBhIGxvb2sgYXQgb3VyIG91dGNvbWUgdmFyaWFibGUgd2UgZmluZCB0aGF0IE91ciBkYXRhIGlzIHVuYmFsYW5jZWQsIGdpdmVuIGFib3V0IDEvNCBvZiB0aGUgb2JzZXJ2YXRpb25zIGxlZnQgdGhlIG9yZ2FuaXphdGlvbiBhbmQgMy80J3Mgc3RheWVkLiBXZSdsbCBuZWVkIHRvIGFjY291bnQgZm9yIHRoaXMgaW4gb3VyIHJlY2lwZXMuIA0KDQojIyBDcmVhdGUgVHJhaW5pbmcgJiBUZXN0IERhdGENCg0KDQo8IS0tIGBgYHtyIEFkZCBJRH0gLS0+DQo8IS0tICMjVEhJUyBJRCBWQVJJQUJMRSBXQVMgR0VORVJBTExZIFBST0JMRU1BVElDIElOIEFMTCBNT0RFTFMuIERFQ0lERUQgTk9UIFRPIFVTRSBJVC4gLS0+DQoNCjwhLS0gI0JlZm9yZSBzcGxpdHRpbmcgdGhlIGRhdGEsIHdlJ2xsIGNyZWF0ZSBhbiBJRCB2YXJpYWJsZSBmb3IgY29udmVuaWVuY2UuICAtLT4NCg0KPCEtLSAjQURESU5HIElEIENPTFVNTiwgTUFLSU5HIElUIEZJUlNUIENPTFVNTiAtLT4NCjwhLS0gIyBEYXRhIDwtIERhdGEgJT4lICAtLT4NCjwhLS0gIyAgICAgbXV0YXRlKElEID0gcm93X251bWJlcigpKSAlPiUgLS0+DQo8IS0tICMgICBzZWxlY3QoSUQsIGV2ZXJ5dGhpbmcoKSkgLS0+DQo8IS0tIGBgYCAtLT4NCg0KPCEtLSBgYGB7ciBSYW5kb20gVmFyfSAtLT4NCjwhLS0gIyNUSElTIFJBTkRPTSBWQVJJQUJMRSBTT1VORFMgTElLRSBBIEdPT0QgSURFQSwgQlVUIE5PVCBTVVJFIEVYQUNUTFkgSE9XIFRPIFVTRSBJVC4gIC0tPg0KDQo8IS0tICMgQW5kIHdlJ2xsIGFkZCBhIGNvbHVtbiB3aXRoIGEgcmFuZG9tIHZhcmlhYmxlIGZvciBsYXRlciBzdGF0aXN0aWNhbCB0ZXN0cy4gLS0+DQo8IS0tICMgRGF0YSA8LSBEYXRhICU+JSAtLT4NCjwhLS0gIyAgIG11dGF0ZShSTkRfVkFSID0gcnVuaWYoOTk5OSwgbWluID0gMCwgbWF4ID0gMSkpICU+JSAgLS0+DQo8IS0tICMgICBzZWxlY3QoSUQsIFJORF9WQVIsIGV2ZXJ5dGhpbmcoKSkgLS0+DQoNCjwhLS0gI0NyZWF0ZSBhIHJhbmRvbSB2YXJpYWJsZSB0byBoZWxwIHdpdGggVmFyaWFibGUgSW1wb3J0YW5jZSBEZWNpc2lvbnMuIFRoaXMgaXMgYSB0aXAgSSBwaWNrZWQgdXAgZnJvbSBhIGNvbGxlYWd1ZS4gQnkgY3JlYXRpbmcgdGhpcyByYW5kb20gdmFyaWFibGUsIGl0IGdpdmVzIHlvdSBhIGRlZmluaXRpdmUgbGluZSB3aGVuIGxvb2tpbmcgYXQgdmFyaWFibGUgaW1wb3J0YW5jZS4gSWYgYSB2YXJpYWJsZSBpcyBsZXNzIGltcG9ydGFudCB0aGFuIHlvdXIgcmFuZG9tIHZhcmlhYmxlLCB0aGVuIHlvdSBrbm93IG5vdCB0byB1c2UgaXQgaW4geW91ciBtb2RlbC4gQWxzbywgaWYgYSB2YXJpYWJsZSBpcyBjbG9zZSB0byB5b3VyIHJhbmRvbSB2YXJpYWJsZSwgeW91IGtub3cgdGhhdCBpdCBwcm9iYWJseSB3b24ndCBiZSB0b28gdXNlZnVsIGluIHRoZSBtb2RlbC4gSXQgaGFzIGhlbHBlZCBtZSB0byByZWFsbHkgZGV0ZXJtaW5lIHdoaWNoIHZhcmlhYmxlcyBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IGFuZCB0byBrZWVwIG15IG1vZGVscyBhcyBwYXJzaW1vbmlvdXMgYXMgcG9zc2libGUuIC0tPg0KPCEtLSBgYGAgLS0+DQoNCkZpcnN0LCBXZSdsbCBtYWtlIG91ciBvdXRjb21lIHZhcmlhYmxlIGEgZmFjdG9yIGZvciBlYXNpZXIgd29ya2luZyBvdXRzaWRlIG9mIHJlY2lwZXMuIA0KDQpgYGB7ciBGYWN0b3JpemUgT3V0Y29tZX0NCkRhdGEgPC0gRGF0YSAlPiUNCiAgbXV0YXRlKGxlZnQgPSBhcy5mYWN0b3IobGVmdCkpDQpgYGANCg0KVGhlbiwgd2UnbGwgc2V0IHNlZWQgYW5kIHNwbGl0IHRoZSBkYXRhLiBXZSdyZSB1c2luZyBhIGRlZmF1bHQgNzUvMjUgc3BsaXQsIGFzIHdlIGhhdmUgYSBuaWNlIGxhcmdlIGFtb3VudCBvZiBkYXRhIGFuZCB0aGlzIHNob3VsZCBsZWF2ZSBlbm91Z2ggZGF0YSBpbiBlYWNoIHNldC4NCg0KYGBge3IgU3BsaXQgRGF0YX0NCnNldC5zZWVkKDEwMTMpDQoNCiNTUExJVCBEQVRBIFdJVEggQ09OU0lERVJBVElPTiBPRiBPVVRDT01FIFZBUklBQkxFIGBMRUZUYA0KZGF0YV9zcGxpdCA8LSBpbml0aWFsX3NwbGl0KERhdGEsIHByb3AgPSAwLjc1LCBzdHJhdGEgPSAibGVmdCIpDQoNCiNDUkVBVEUgVFJBSU5JTkcgJiBURVNUIERBVEEgU0VUUw0KdHJhaW5fZGF0YSA8LSB0cmFpbmluZyhkYXRhX3NwbGl0KQ0KdGVzdF9kYXRhIDwtIHRlc3RpbmcoZGF0YV9zcGxpdCkNCg0KI0RPVUJMRSBDSEVDSyBPVVRDT01FIFNQTElUIElOIFRSQUlOSU5HICYgVEVTVCBEQVRBIFNFVFMNCnRhYnlsKHRyYWluX2RhdGEkbGVmdCkNCnRhYnlsKHRlc3RfZGF0YSRsZWZ0KQ0KYGBgDQoNClRyYWluaW5nIGRhdGEgYW5kIHRlc3QgZGF0YSBzZXRzIHdlcmUgY3JlYXRlZCB3aXRoIGEgNzUgLyAyNSBzcGxpdCBvZiBvcmlnaW5hbCBvYnNlcnZhdGlvbnMuIER1ZSB0byBpbWJhbGFuY2UsIHdlIHNwZWNpZmllZCBvdXIgb3V0Y29tZSB2YXJpYWJsZSAoYGxlZnRgKSB3aGVuIHNwbGl0dGluZyB0byBlbnN1cmUgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cyBoYWQgYW4gYXBwcm94aW1hdGVseSBlcXVhbCBudW1iZXIgb2YgcmV0ZW50aW9uICYgYXR0cml0aW9uIGluIHRoZSBvdXRvbWUgdmFyaWFibGUuIENoZWNraW5nIHRoZSByZXN1bHRpbmcgc2V0cyBzaG93cyB1cyB3ZSBzdWNjZXNzZnVsbHkgaGVsZCB0aGUgNzYlLzI0JSByYXRpbyBvZiBgbGVmdGAgTm8vWWVzIG9ic2VydmF0aW9ucy4NCg0KVGhpcyBpbWJhbGFuY2UgaW4gVHJhaW5pbmcgc2V0IHdpbGwgYmUgYWNjb3VudGVkIGZvciBpbiBtb2RlbCBwcmUtcHJvY2Vzc2luZyANCg0KIyMgQ3Jvc3MgVmFsaWRhdGlvbiBWLUZvbGRzIGNyZWF0aW9uDQoNCk5vdyB0byBnbyBhaGVhZCBhbmQgY3JlYXRlIG91ciBzcGxpdHMgdG8gdXNlIGluIG1vZGVsaW5nIGxhdGVyLg0KDQpgYGB7ciBWIEZvbGRzIERhdGF9DQpzZXQuc2VlZCgxMDEzKQ0KY3ZfZm9sZHMgPC0gdmZvbGRfY3YodHJhaW5fZGF0YSwgc3RyYXRhID0gbGVmdCwgdiA9IDEwKSANCg0KbWFwX2RibChjdl9mb2xkcyRzcGxpdHMsDQogICAgICAgIGZ1bmN0aW9uKHgpIHsNCiAgICAgICAgICBkYXQgPC0gYXMuZGF0YS5mcmFtZSh4KSRsZWZ0DQogICAgICAgICAgbWVhbihkYXQgPT0gIlllcyIpDQogICAgICAgIH0pDQoNCmBgYA0KDQoNCiMgTW9kZWxzIA0KDQpUbyBhY2hpZXZlIG91ciBmaW5hbCBnb2FsIG9mIGZpbmRpbmcgYSBwcmVkaWN0aXZlIG1vZGVsIGZvciBhdHRyaXRpb24gdGhhdCBjYW4gYmUgdXNlZCB0byBxdWFudGlmeSB0aGUgUk9JIG9mIGFuIGludGVydmVudGlvbiwgd2UnbGwgdHJ5IHNldmVyYWwgbW9kZWxzLiBPdXIgZ29hbCBpcyBib3RoIHRoZSBtb3N0IHByZWRpY3RpdmUgbW9kZWwsIGJ1dCBhbHNvIG9uZSB0aGF0IGlzIGludGVycHJldGFibGUuIFdlJ2xsIHN0YXJ0IHdpdGggYSBtb3JlIGNvbXBsZXggbW9kZWwgdG8gc2VlIGhvdyBwcmVkaWN0aXZlIGl0IGlzIGFuZCB0aGVuIHJ1biBhIHNpbXBsZXIgbW9kZWwgdG8gY29tcGFyZSBlZmZlY3RpdmVuZXNzLiANCg0KIyMgTW9kZWwgJiBSZWNpcGUgUHJlcA0KDQpGaXJzdCB3ZSdsbCBwcmVwIHNldmVyYWwgbW9kZWwgc3BlY2lmaWNhdGlvbnMuDQoNCmBgYHtyIE1vZGVsc30NCg0KI0NSRUFUSU5HIE1PREVMUyBGT1IgU0NSRUVOSU5HDQojSGVscGZ1bCBtb2RlbCBjaG9pY2UgYXBwcm9hY2ggY2hhcnQ6IGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvdHV0b3JpYWwvbWFjaGluZV9sZWFybmluZ19tYXAvaW5kZXguaHRtbA0KDQojTG9naXN0aWMgUmVncmVzc2lvbg0KI1NJTVBMRVNUIA0Kc3BlY19sb2dpc3RpYyA8LSANCiAgbG9naXN0aWNfcmVnKHBlbmFsdHkgPSB0dW5lKCksIG1peHR1cmUgPSB0dW5lKCkpICU+JSANCiAgc2V0X2VuZ2luZSgiZ2xtIikgJT4lIA0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KDQojUmFuZG9tIEZvcmVzdCAtIERlY2lzaW9uIFRyZWUgTWV0aG9kDQojTU9SRSBDT01QTEVYDQpzcGVjX3JmIDwtIA0KICByYW5kX2ZvcmVzdChtdHJ5ID0gNSwgdHJlZXMgPSAxMDAwMCwgbWluX24gPSA0KSAlPiUgICNSZXBsYWNlIHdpdGggdHJlZXMgPSAjdHVuZSgpDQogIHNldF9lbmdpbmUoInJhbmRvbUZvcmVzdCIsIGltcG9ydGFuY2UgPSBUUlVFKSAlPiUgICNzZXRfZW5naW5lKCJyYW5nZXIiLCBpbXBvcnRhbmNlID0gImltcHVyaXR5KQ0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUgDQogIHBhcnNuaXA6OnRyYW5zbGF0ZSgpDQoNCiNOZXVyYWwgTmV0DQojTU9SRSBDT01QTEVYDQpzcGVjX25uZXQgPC0gDQogICBtbHAoKSAlPiUgDQogICBzZXRfZW5naW5lKCJrZXJhcyIsIHZlcmJvc2UgPSAwKSAlPiUgDQogICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KDQojU3VwcG9ydCBWZWN0b3IgTWFjaGluZSAoTGluZWFyU1YgQ2xhc3NpZmljYXRpb24pDQojTU9SRSBDT01QTEVYDQpzcGVjX3N2bV9saW5lYXIyIDwtDQogIHN2bV9saW5lYXIoY29zdCA9IDEpICU+JQ0KICBzZXRfZW5naW5lKCJrZXJubGFiIikgJT4lDQogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpDQoNCg0KI1NPTUUgT1RIRVIgTU9ERUwgSURFQVMNCiMgZHRfbG9hbiA8LSBkZWNpc2lvbl90cmVlKGNvc3RfY29tcGxleGl0eSA9IHR1bmUoKSwgdHJlZV9kZXB0aCA9IHR1bmUoKSwgbWluX24gPSB0dW5lKCkpICU+JSANCiMgICBzZXRfZW5naW5lKCJycGFydCIpICU+JSANCiMgICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KIyANCiMgeGdib29zdF9sb2FuIDwtIGJvb3N0X3RyZWUobXRyeSA9IHR1bmUoKSwgdHJlZXMgPSB0dW5lKCksIG1pbl9uID0gdHVuZSgpLCB0cmVlX2RlcHRoID0gdHVuZSgpLCBsZWFybl9yYXRlID0gdHVuZSgpLCBsb3NzX3JlZHVjdGlvbiA9IHR1bmUoKSwgc2FtcGxlX3NpemUgPSB0dW5lKCkpICAlPiUgDQojICAgc2V0X2VuZ2luZSgieGdib29zdCIpICU+JSANCiMgICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KIyANCiMgI0stTmVhcmVzdCBOZWlnaGJvcnMgLSBUaGlzIGlzIGEgdHlwZSBvZiBTVk0NCiMga25uX2xvYW4gPC0gbmVhcmVzdF9uZWlnaGJvcihuZWlnaGJvcnMgPSB0dW5lKCksIHdlaWdodF9mdW5jID0gdHVuZSgpLCBkaXN0X3Bvd2VyID0gdHVuZSgpKSAlPiUgDQojICAgc2V0X2VuZ2luZSgia2tubiIpICU+JSANCiMgICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KDQojIHNwZWNfcmYgPC0gDQojICAgcmFuZF9mb3Jlc3QobXRyeSA9IHR1bmUoKSwgdHJlZXMgPSAxMDAwLCBtaW5fbiA9IHR1bmUoKSkgJT4lICAjUmVwbGFjZSB3aXRoIHRyZWVzID0gI3R1bmUoKQ0KIyAgIHNldF9lbmdpbmUoInJhbmRvbUZvcmVzdCIsIGltcG9ydGFuY2UgPSBUUlVFKSAlPiUgDQojICAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikNCg0KIyBzcGVjX3N2bV9saW5lYXIgPC0gDQojICAgc3ZtX3BvbHkoZGVncmVlID0gMSkgJT4lIA0KIyAgIHNldF9lbmdpbmUoImtlcm5sYWIiKSAlPiUgDQojICAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikNCmBgYA0KDQpUaGVuIHdlJ2xsIGNyZWF0ZSByZWNpcGVzIHRoYXQgY2FuIGhhbmRsZSBwcmVwcm9jZXNzaW5nIG9mIG91ciBkYXRhLiBHaXZlbiB0aGUgaW1iYWxhbmNlIGluIHRoZSBvdXRjb21lIHZhcmlhYmxlLCB3ZSdsbCBhZGQgc3RlcHMgdG8gY29ycmVjdCBpbiBvdXIgdHJhaW5pbmcgZGF0YS4gV2UnbGwgYWxzbyBjZW50ZXIgYW5kIHNjYWxlIChub3JtYWxpemUpIGRhdGEgc2luY2Ugd2Ugc2F3IHRoZXJlIHdhcyBhIGdvb2QgYml0IG9mIG5vbi1ub3JtYWxuZXNzIGFuZCBiaWZ1cmNhdGVkIGNhdGVnb3JpemF0aW9ucyBpbiBwcmVkaWN0b3IgdmFyaWFibGVzLiANCg0KYGBge3IgUmVjaXBlc30NCiNDUkVBVEUgRk9VUiBSRUNJUEVTDQoNCiMiU29tZSBtb2RlbHMgKG5vdGFibHkgbmV1cmFsIG5ldHdvcmtzLCBLLW5lYXJlc3QgbmVpZ2hib3JzLCBhbmQgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZXMpIHJlcXVpcmUgcHJlZGljdG9ycyB0aGF0IGhhdmUgYmVlbiBjZW50ZXJlZCBhbmQgc2NhbGVkIiAtIGh0dHBzOi8vd3d3LnRtd3Iub3JnL3dvcmtmbG93LXNldHMuaHRtbA0KDQojUFJFUFJPQ0VTU0lORyBSVUxFUyBGUk9NIEtVSE4sIHA1NTANCiNMT0dJU1RJQyBSRUdSRVNTSU9OIC0gQ1MsIE5aViwgUkVNT1ZFIENPUlIgVkFSUw0KI05FVVJBTCBORVQgLSBDUywgTlpWLCBSRU1PVkUgQ09SUiBWQVJTDQojU1ZNIC0gQ1MNCiNSQU5ET00gRk9SRVNUIC0gTi9BDQoNCnNldC5zZWVkKDEwMTMpDQoNCmNvbmZsaWN0X3ByZWZlcigic3RlcF91cHNhbXBsZSIsICJ0aGVtaXMiKQ0KDQojUkVDSVBFIDEgLSBDZW50ZXJlZCAmIFNjYWxlZC1Cb3hDb3g7IFVwc2FtcGxlOyBOWlYNCnJlY2lwZV8xIDwtIHJlY2lwZShsZWZ0IH4uLCBkYXRhID0gdHJhaW5fZGF0YSkgJT4lIA0KICAjdXBkYXRlX3JvbGUoSUQsIG5ld19yb2xlID0gImlkIikgJT4lIA0KICBzdGVwX0JveENveChhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpJT4lDQogIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUgDQogIHN0ZXBfbnp2KGFsbF9wcmVkaWN0b3JzKCkpICU+JQ0KICBzdGVwX3Vwc2FtcGxlKGxlZnQsIHNraXAgPSBUUlVFKSAgDQogIA0KI1JFQ0lQRSAyIC0gQ2VudGVyZWQgJiBTY2FsZWQ7IFVwc2FtcGxlOyBOWlYNCnJlY2lwZV8yIDwtIHJlY2lwZShsZWZ0IH4uLCBkYXRhID0gdHJhaW5fZGF0YSkgJT4lIA0KICAjdXBkYXRlX3JvbGUoSUQsIG5ld19yb2xlID0gImlkIikgJT4lDQogIHN0ZXBfY2VudGVyKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lIA0KICBzdGVwX3NjYWxlKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUgDQogIHN0ZXBfbnp2KGFsbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgc3RlcF91cHNhbXBsZShsZWZ0LCBza2lwID0gVFJVRSkNCg0KI1JFQ0lQRSAzIC0gTm90IENlbnRlcmVkICYgU2NhbGVkOyBVcHNhbXBsZQ0KcmVjaXBlXzMgPC0gcmVjaXBlKGxlZnQgfi4sIGRhdGEgPSB0cmFpbl9kYXRhKSAlPiUgDQogIyB1cGRhdGVfcm9sZShJRCwgbmV3X3JvbGUgPSAiaWQiKSAlPiUgICAgICAjIyBQZXIgSnVsaWEgU2lsZ2UNCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgc3RlcF91cHNhbXBsZShsZWZ0LCBza2lwID0gVFJVRSkNCg0KI1JFQ0lQRSA0IC0gTm90IENlbnRlcmVkICYgU2NhbGVkOyBVcHNhbXBsZTsgTlpWDQpyZWNpcGVfNCA8LSByZWNpcGUobGVmdCB+LiwgZGF0YSA9IHRyYWluX2RhdGEpICU+JSANCiAgI3VwZGF0ZV9yb2xlKElELCBuZXdfcm9sZSA9ICJpZCIpICAlPiUNCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgc3RlcF9uenYoYWxsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICBzdGVwX3Vwc2FtcGxlKGxlZnQsIHNraXAgPSBUUlVFKQ0KICANCmBgYA0KDQojIyAxIFN1cHBvcnQgVmVjdG9yIENsYXNzaWZpZXINCg0KV2UnbGwgc3RhcnQgYnkgcnVubmluZyBhIGNvbXBsaWNhdGVkIG1vZGVsIHRvIHNldCAiY2VpbGluZyBwb3RlbnRpYWwiIGZvciBwcmVkaWN0aW9uIGFjY3VyYWN5Lg0KDQpgYGB7ciBTVk0gV29ya2Zsb3d9DQpzdm1fd29ya2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUgDQogIGFkZF9yZWNpcGUocmVjaXBlXzIpICU+JSANCiAgYWRkX21vZGVsKHNwZWNfc3ZtX2xpbmVhcjIpDQogIA0Kc3ZtX3dvcmtmbG93DQpgYGANCg0KYGBge3IgU1ZNIFJlc30NCiNSRVNJRFVBTFMgV0lUSE9VVCBUVU5JTkcNCmNvbmZsaWN0X3ByZWZlcigiYWxwaGEiLCAic2NhbGVzIikNCg0Kc3ZtX3JlcyA8LSANCiAgc3ZtX3dvcmtmbG93ICU+JSANCiAgZml0X3Jlc2FtcGxlcygNCiAgICByZXNhbXBsZXMgPSBjdl9mb2xkcywgDQogICAgbWV0cmljcyA9IG1ldHJpY19zZXQoDQogICAgICByZWNhbGwsIHByZWNpc2lvbiwgZl9tZWFzLCANCiAgICAgIGFjY3VyYWN5LCBrYXAsDQogICAgICByb2NfYXVjLCBzZW5zLCBzcGVjKSwNCiAgICBjb250cm9sID0gY29udHJvbF9yZXNhbXBsZXMoDQogICAgICBzYXZlX3ByZWQgPSBUUlVFKQ0KICAgICkgDQoNCnN2bV9yZXMNCmBgYA0KDQoNCmBgYHtyIFNWTSBGaXR9DQojRklUIFRIRSBNT0RFTCBXSVRIIFRSQUlOSU5HIERBVEENCnN2bV9maXQgPC0gc3ZtX3dvcmtmbG93ICU+JSANCiAgZml0KGRhdGEgPSB0cmFpbl9kYXRhKQ0KDQpzdm1fZml0DQogIA0KYGBgDQoNCiogV2l0aG91dCBhbnkgaW5wdXQsIHRoZSBtb2RlbCBoYXMgYW4gZXJyb3IgcmF0ZSBvZiAuMjEsIHdoaWNoIGlzIGEgbGl0dGxlIGJldHRlciB0aGFuIGNoYW5jZSAoLjI0KSBidXQgbm90IG11Y2guDQoNCg0KYGBge3IgU1ZNIFR1bmluZyAtIEFVQyBQbG90fQ0KI1VzaW5nIGEgdHVuaW5nIGdyaWQgdG8gZmluZCB0aGUgdmFsdWUgb2YgYGNvc3RgIHRoYXQgbGVhZHMgdG8gdGhlIGhpZ2hlc3QgYWNjdXJhY3kgZm9yIHRoZSBTVk0gbW9kZWwNCg0Kc2V0LnNlZWQoMTAxMykNCg0KI05FVyBXT1JLRkxPVyBXSVRIIFRVTkUgUEFSQU0NCnN2bV90dW5lX3dmIDwtIHdvcmtmbG93KCkgJT4lDQogIGFkZF9tb2RlbChzcGVjX3N2bV9saW5lYXIyICU+JSBzZXRfYXJncyhjb3N0ID0gdHVuZSgpKSkgJT4lDQogIGFkZF9mb3JtdWxhKGxlZnQgfiAuKQ0KDQojQ1JPU1MgVkFMSURBVEUgTU9ERUwgV0lUSCBWRk9MRFMNCnNpbV9kYXRhX2ZvbGQgPC0gdmZvbGRfY3YodHJhaW5fZGF0YSwgc3RyYXRhID0gbGVmdCkNCg0KcGFyYW1fZ3JpZCA8LSBncmlkX3JlZ3VsYXIoY29zdCgpLCBsZXZlbHMgPSAxMCkNCg0KY29uZmxpY3RfcHJlZmVyKCJhbHBoYSIsICJzY2FsZXMiKQ0KDQojUExPVCBUVU5FRCBSRVNJRFVBTFMNCnR1bmVfcmVzIDwtIHR1bmVfZ3JpZCgNCiAgc3ZtX3R1bmVfd2YsIA0KICByZXNhbXBsZXMgPSBzaW1fZGF0YV9mb2xkLCANCiAgZ3JpZCA9IHBhcmFtX2dyaWQNCikNCmF1dG9wbG90KHR1bmVfcmVzKQ0KYGBgDQoNCmBgYHtyIFNWTSBUdW5pbmcgLSBGaW5hbGl6ZX0NCiNQbHVnZ2luZyBpbiBiZXN0IHZhbHVlIGZvciBjb3N0IGZvciBmaW5hbCBtb2RlbCBmaXQNCmJlc3RfY29zdF9zdm0gPC0gc2VsZWN0X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9ICJyb2NfYXVjIikNCg0Kc3ZtX2xpbmVhcl9maW5hbCA8LSBmaW5hbGl6ZV93b3JrZmxvdyhzdm1fd29ya2Zsb3csIGJlc3RfY29zdF9zdm0pDQoNCnN2bV9saW5lYXJfZml0IDwtIHN2bV9saW5lYXJfZmluYWwgJT4lIGZpdCh0cmFpbl9kYXRhKQ0KDQpzdm1fbGluZWFyX2ZpdA0KYGBgDQoNCg0KDQpgYGB7ciBTVk0gQ29uZnVzaW9uIE1hdHJpeH0NCiNDT05GVVNJT04gTUFUUklYIFdJVEggVFVORUQgQ09TVA0KDQpzZXQuc2VlZCgxMDEzKQ0KDQphdWdtZW50KHN2bV9saW5lYXJfZml0LCBuZXdfZGF0YSA9IHRlc3RfZGF0YSkgJT4lDQogIGNvbmZfbWF0KHRydXRoID0gbGVmdCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykNCg0KDQojICAgICAgICAgICBUcnV0aA0KIyBQcmVkaWN0aW9uICAgIDAgICAgMQ0KIyAgICAgICAgICAwIDE3OTEgIDQzNA0KIyAgICAgICAgICAxICAxMTggIDE1Nw0KYGBgDQoNCipUTyBETyAtIFVQREFURSBUSElTIERFU0NSSVBUSU9OIEFGVEVSIFJFUlVOTklORyoNCiogNTMuOSUgKG49MTM0OSkgUHJlZGljdGlvbiB3YXMgTm8gQXR0cml0aW9uLCBUcnV0aCB3YXMgTm8gQXR0cml0aW9uDQoqIDYuMyUgKG49NTA4KSBQcmVkaWN0aW9uIHdhcyBBdHRyaXRpb24sIFRydXRoIHdhcyBBdHRyaXRpb24NCg0KNzcuOSUgQWNjdXJhdGUgcHJlZGljdGlvbg0KDQoqIDQuNyUgKG49NTYwKSBQcmVkaWN0aW9uIHdhcyBBdHRyaXRpb24sIFRydXRoIHdhcyBObyBBdHRyaXRpb24NCiogMTcuNCUgKG49ODMpIFByZWRpY3Rpb24gd2FzIE5vIEF0dHJpdGlvbiwgVHJ1dGggd2FzIEF0dHJpdGlvbg0KDQoyMi4xJSBJbmFjY3VyYXRlIHByZWRpY3Rpb24NCg0KSW4gYW4gUk9JIHNjZW5hcmlvIC0gdGhpcyBtb2RlbCB3b3VsZCANCg0KYGBge3IgU1ZNIENvbmZ1c2lvbiBNYXRyaXggLSBTdW1tYXJ5fQ0KYXVnbWVudChzdm1fbGluZWFyX2ZpdCwgbmV3X2RhdGEgPSB0ZXN0X2RhdGEpICU+JQ0KICBjb25mX21hdCh0cnV0aCA9IGxlZnQsIGVzdGltYXRlID0gLnByZWRfY2xhc3MpICU+JSANCiAgc3VtbWFyeSgpDQpgYGANCg0KDQoNCiMjIDIgTG9naXN0aWMgUmVncmVzc2lvbg0KDQpBZnRlciBydW5uaW5nIGEgbW9yZSBjb21wbGljYXRlZCBTVk0gbW9kZWwgdG8gc2VlIGhvdyBnb29kIGl0IGNvdWxkIGJlLCB3ZSdsbCBydW4gYSBzaW1wbGUgTG9naXN0aWMgUmVncmVzc2lvbiB0byBjb21wYXJlIGFuZCBjb250cmFzdC4gDQoNCg0KYGBge3IgTG9naXQgV29ya2Zsb3d9DQojTE9HSVNUSUMgUkVHUkVTU0lPTiBXT1JLRkxPVw0KbG9naXRfd2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUNCiAgIGFkZF9yZWNpcGUocmVjaXBlXzIpICU+JQ0KICAgYWRkX21vZGVsKHNwZWNfbG9naXN0aWMpDQoNCmxvZ2l0X3dmbG93DQpgYGANCg0KDQpgYGB7ciBMb2dpdCBSZXN9DQojUkVTSURVQUxTIFdJVEhPVVQgVFVOSU5HDQpsb2dfcmVzIDwtIA0KICBsb2dpdF93ZmxvdyAlPiUgDQogIGZpdF9yZXNhbXBsZXMoDQogICAgcmVzYW1wbGVzID0gY3ZfZm9sZHMsIA0KICAgIG1ldHJpY3MgPSBtZXRyaWNfc2V0KA0KICAgICAgcmVjYWxsLCBwcmVjaXNpb24sIGZfbWVhcywgDQogICAgICBhY2N1cmFjeSwga2FwLA0KICAgICAgcm9jX2F1Yywgc2Vucywgc3BlYyksDQogICAgY29udHJvbCA9IGNvbnRyb2xfcmVzYW1wbGVzKA0KICAgICAgc2F2ZV9wcmVkID0gVFJVRSkNCiAgICApIA0KDQpsb2dfcmVzDQpgYGANCg0KDQpgYGB7ciBMb2dpdCBDb2VmZmljaWVudHN9DQojIHNhdmUgbW9kZWwgY29lZmZpY2llbnRzIGZvciBhIGZpdHRlZCBtb2RlbCBvYmplY3QgZnJvbSBhIHdvcmtmbG93DQoNCmdldF9tb2RlbCA8LSBmdW5jdGlvbih4KSB7DQogIHB1bGxfd29ya2Zsb3dfZml0KHgpICU+JSB0aWR5KCkNCn0NCg0KIyBzYW1lIGFzIGJlZm9yZSB3aXRoIG9uZSBleGNlcHRpb24NCmxvZ19yZXNfMiA8LSANCiAgbG9naXRfd2Zsb3cgJT4lIA0KICBmaXRfcmVzYW1wbGVzKA0KICAgIHJlc2FtcGxlcyA9IGN2X2ZvbGRzLCANCiAgICBtZXRyaWNzID0gbWV0cmljX3NldCgNCiAgICAgIHJlY2FsbCwgcHJlY2lzaW9uLCBmX21lYXMsIA0KICAgICAgYWNjdXJhY3ksIGthcCwNCiAgICAgIHJvY19hdWMsIHNlbnMsIHNwZWMpLA0KICAgIGNvbnRyb2wgPSBjb250cm9sX3Jlc2FtcGxlcygNCiAgICAgIHNhdmVfcHJlZCA9IFRSVUUsDQogICAgICBleHRyYWN0ID0gZ2V0X21vZGVsKSAjIHVzZSBleHRyYWN0IGFuZCBvdXIgbmV3IGZ1bmN0aW9uDQogICAgKSANCg0KYWxsX2NvZWYgPC0gbWFwX2Rmcihsb2dfcmVzXzIkLmV4dHJhY3RzLCB+IC54W1sxXV1bWzFdXSkgI1Jlc3VsdHMgZmxhdHRlbmVkIGFuZCBjb2xsZWN0ZWQNCg0KbG9nX3Jlc18yJC5leHRyYWN0c1tbMV1dW1sxXV0gI1Nob3cgcmVzdWx0cw0KDQpgYGANCg0KDQoqIFNhdGlzZmFjdGlvbiBMZXZlbCwgVGltZSBhdCBDb21wYW55LCBXb3JrIEFjY2lkZW50LCBOdW1iZXIgb2YgUHJvamVjdHMsIEhpZ2ggU2FsYXJ5IGFyZSB0aGUgdG9wIHByZWRpY3RvciB2YXJpYWJsZXMuIE1vc3Qgb2YgdGhlc2Ugd29uJ3QgYmUgYWRkcmVzc2FibGUgYnkgYW4gaW50ZXJ2ZW50aW9uLiBXZSBtYXkgbmVlZCB0byBkbyBzb21lIGZlYXR1cmUgZW5naW5lZXJpbmcgdG8gbWFrZSB0aGUgbW9kZWwgbW9yZSByZWxldmFudCB0byBvdXIgdXNlIGNhc2UuIA0KDQpgYGB7ciBMb2dpdCBLIEZvbGRzfQ0Kc2V0LnNlZWQoMTAxMykNCg0KI0ZpdCB3aXRoIGZvcm11bGEgYW5kIG1vZGVsDQojQ1JPU1MgVkFMSURBVEUgTE9HSVRfV0tGTE9XIE9OIENWIEZPTERTDQoNCmZpdF9yZXNhbXBsZXMoDQogIGxvZ2l0X3dmbG93LA0KICBtb2RlbCA9IHNwZWNfbG9naXN0aWMsICAgICAgICAgIA0KICByZXNhbXBsZXMgPSBjdl9mb2xkcw0KKQ0KYGBgDQoNCg0KYGBge3IgTG9naXQgSyBGb2xkcyAtIFJlc3VsdHN9DQoNCiNQUklOVCBSRVNVTFRTDQojc0FWRSBBUyBPYmplY3QNCg0Kc2V0LnNlZWQoMTAxMykgDQoNCmxvZ2l0X3R1bmVfcmVzdWx0cyA8LSBmaXRfcmVzYW1wbGVzKGxvZ2l0X3dmbG93LCANCiAgICAgICAgICAgICAgc3BlY19sb2dpc3RpYywgDQogICAgICAgICAgICAgIHJlc2FtcGxlcyA9IGN2X2ZvbGRzKSAlPiUNCiAgY29sbGVjdF9tZXRyaWNzKCkNCmBgYA0KDQoNCg0KDQpgYGB7ciBMb2dpdCBGaXQgLSBGaW5hbH0NCmxvZ2l0X2xhc3RfZml0IDwtIGxvZ2l0X3dmbG93ICU+JQ0KICAjIGZpdCBvbiB0aGUgdHJhaW5pbmcgc2V0IGFuZCBldmFsdWF0ZSBvbiB0aGUgdGVzdCBzZXQNCiAgbGFzdF9maXQoZGF0YV9zcGxpdCkNCg0KbG9naXRfbGFzdF9maXQNCmBgYA0KDQoNCmBgYHtyIExvZ2l0IFRlc3QgRGF0YSBQZXJmfQ0KbG9naXRfdGVzdF9wZXJmb3JtYW5jZSA8LSBsb2dpdF9sYXN0X2ZpdCAlPiUgY29sbGVjdF9tZXRyaWNzKCkNCmxvZ2l0X3Rlc3RfcGVyZm9ybWFuY2UNCmBgYA0KDQpPdmVyYWxsIHRoZSBwZXJmb3JtYW5jZSBpcyBub3QgZ3JlYXQuIFNpbWlsYXIgdG8gb3VyIHRyYWluaW5nIGRhdGEsIGFjY3VyYWN5IG9mIC43NCBhbmQgQVVDIG9mIC44MS4gQWNjdXJhY3kgaXMgZXZlbiB3aXRoIG91ciBiYXNlbGluZSBvZiA3NCUgYW5kIFJPQyBpcyBoaWdoZXIgYnkgNiUuIFRoaXMgbWF5IGJlIHZhbHVhYmxlIGZvciBvdXIgcHVycG9zZXMsIGRlcGVuZGluZyBvbiB3aGVyZSB0aGUgYWNjdXJhY3kgaXMgY29taW5nIGZyb20uIA0KDQoNCg0KYGBge3IgTG9naXQgUHJlZGljdGlvbnN9DQojR2VuZXJhdGUgdGVzdCBwcmVkaWN0aW9ucyBmcm9tIHRoZSB0ZXN0IHNldC4NCg0KbG9naXRfdGVzdF9wcmVkaWN0aW9ucyA8LSBsb2dpdF9sYXN0X2ZpdCAlPiUgY29sbGVjdF9wcmVkaWN0aW9ucw0KbG9naXRfdGVzdF9wcmVkaWN0aW9ucw0KYGBgDQoNCg0KYGBge3IgTG9naXQgUk9DfQ0KI1Bsb3QgdGhlIFJPQyBDdXJ2ZQ0KbG9naXRfdGVzdF9wcmVkaWN0aW9ucyAlPiUNCiAgcm9jX2N1cnZlKGxlZnQsIC5wcmVkXzApICU+JSAjT3JpZ2luYWxseSwgd2FzICJwcmVkX1llcyIgYW5kIGN1cnZlIHdhcyBpbnZlcnRlZA0KICBnZ3Bsb3QoYWVzKHggPSAxIC0gc3BlY2lmaWNpdHksIHkgPSBzZW5zaXRpdml0eSkpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjUsIGNvbG9yID0gIm1pZG5pZ2h0Ymx1ZSIpICsNCiAgZ2VvbV9hYmxpbmUoDQogICAgbHR5ID0gMiwgYWxwaGEgPSAwLjUsDQogICAgY29sb3IgPSAiZ3JheTUwIiwNCiAgICBzaXplID0gMS4yDQogICkgDQpgYGANCg0KDQpgYGB7ciBMb2dpdCBDb25mdXNpb24gTWF0cml4fQ0KIyBnZW5lcmF0ZSBhIGNvbmZ1c2lvbiBtYXRyaXgNCmNvbmZsaWN0X3ByZWZlcigic3BlYyIsICJ5YXJkc3RpY2siKQ0KDQoNCmxvZ2l0X3Rlc3RfcHJlZGljdGlvbnMgJT4lDQogIGNvbmZfbWF0KHRydXRoID0gbGVmdCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykNCg0KIyAgICAgICAgICAgVHJ1dGgNCiMgUHJlZGljdGlvbiAgICAwICAgIDENCiMgICAgICAgICAgMCAxMzg5ICAxMjQNCiMgICAgICAgICAgMSAgNTIwICA0NjcNCg0KYGBgDQoNCg0KDQpSRVNVTFRTDQoNCiogNTUuNSUgKG49MTM4OSkgUHJlZGljdGlvbiB3YXMgTm8gQXR0cml0aW9uLCBUcnV0aCB3YXMgTm8gQXR0cml0aW9uDQoNCiogMTguNyUgKG49NDY3KSBQcmVkaWN0aW9uIHdhcyBBdHRyaXRpb24sIFRydXRoIHdhcyBBdHRyaXRpb24NCg0KNzQuMiUgQWNjdXJhdGUgcHJlZGljdGlvbg0KDQoqIDIwLjglIChuPTUyMCkgUHJlZGljdGlvbiB3YXMgQXR0cml0aW9uLCBUcnV0aCB3YXMgTm8gQXR0cml0aW9uDQoNCiogNS4wJSAobj0xMjQpIFByZWRpY3Rpb24gd2FzIE5vIEF0dHJpdGlvbiwgVHJ1dGggd2FzIEF0dHJpdGlvbg0KDQoyNS44JSBJbmFjY3VyYXRlIHByZWRpY3Rpb24NCg0KVGhpcyBpcyBzbGlnaHRseSB3b3JzZSB0aGFuIG91ciBtb3JlIGNvbXBsaWNhdGVkIFNWTSBtb2RlbCBpbiBvdmVyYWxsIHByZWRpY3Rpb24gYWNjdXJhY3ksIGJ1dCBub3QgYnkgbXVjaC4gVGhpcyBtb2RlbCBoYXMgYSBtdWNoIHNtYWxsZXIgcGVyY2VudGFnZSBvZiBGYWxzZSBOZWdhdGl2ZXMgdGhhbiB0aGUgU1ZNIG1vZGVsLCB3aGljaCBpcyBtb3JlIHVzZWZ1bCB3aGVuIHByZWRpY3RpbmcgYXR0cml0aW9uLCBnaXZlbiB3ZSB3aWxsIHdhbnQgdG8gYmUgc3VyZSBvdXIgcHJvcG9zZWQgaW50ZXJ2ZW50aW9uIFJPSSBpcyBub3Qgb3ZlcmVzdGltYXRlZC4NCg0KDQoNCiMjIyBNb3JlIExvZ2lzdGljIFJlZ3Jlc3Npb24gQW5hbHlzaXMNCg0KDQoNCg0KYGBge3IgTG9naXQgR2dwbG90fQ0KbG9naXRfdGVzdF9wcmVkaWN0aW9ucyAlPiUgDQogIGdncGxvdCgpICsNCiAgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gLnByZWRfMSwgDQogICAgICAgICAgICAgICAgICAgZmlsbCA9IGxlZnQpLCANCiAgICAgICAgICAgICAgIGFscGhhID0gMC41KQ0KYGBgDQoNCmBgYHtyIExvZ2l0IENvbmZ1c2lvbiBNYXRyaXggU3VtbWFyeX0NCmxvZ2l0X3Rlc3RfcHJlZGljdGlvbnMgJT4lDQogIGNvbmZfbWF0KHRydXRoID0gbGVmdCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykgJT4lDQogIHN1bW1hcnkoKQ0KYGBgDQoNCiogU3BlY2ZpY2l0eSA9IC43OSAoSWYgd2Ugc2F5IHNvbWVvbmUgaXMgZ29pbmcgdG8gbGVhdmUsIHdlIGdldCBpdCByaWdodCB3aXRoIHRoaXMgbW9kZWwgNzklLikNCg0KDQoNCmBgYHtyIExvZ2l0IFZhcmlhYmxlIEltcG9ydGFuY2V9DQojTElTVCBJTVBPUlRBTlQgVkFSSUFCTEVTDQpsb2dpdF9maW5hbF9tb2RlbCA8LSBmaXQobG9naXRfd2Zsb3csIERhdGEpDQoNCmxvZ2l0X2ZpbmFsX21vZGVsDQpgYGANCg0KDQojIyAzIFJhbmRvbSBGb3Jlc3QNCg0KDQpgYGB7ciBSRiBXb3JrZmxvd30NCnJmX3dvcmtmbG93IDwtIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfcmVjaXBlKHJlY2lwZV8zKSAlPiUgDQogIGFkZF9tb2RlbChzcGVjX3JmKQ0KICANCnJmX3dvcmtmbG93DQpgYGANCg0KDQpgYGB7ciBSRiBSZXN9DQojRkFJTElORyAtIE1ZIEJFU1QgR09PR0xJTkcgU1VHR0VTVFMgVEhJUyBJUyBBIEJVRyBXSVRIIFdJTkRPV1MgQU5EIFRIRSBUVU5FL1JFQ0lQRVMgUEFDS0FHRS4gRk9MTE9XSU5HIFdPUktBUk9VTkQgQURWSUNFIE9OTElORSBESUQgTk9UIFJFTUVEWQ0KDQojIGNvbmZsaWN0X3ByZWZlcigicmVjYWxsIiwgIk1ldHJpY3MiKQ0KIyBjb25mbGljdF9wcmVmZXIoInNlbGVjdCIsICJkcGx5ciIpDQojIGNvbmZsaWN0X3ByZWZlcigiZmlsdGVyIiwgImRwbHlyIikNCiMgY29uZmxpY3RfcHJlZmVyKCJwcmVjaXNpb24iLCAiTWV0cmljcyIpDQojIA0KcmZfcmVzIDwtDQogICByZl93b3JrZmxvdyAlPiUNCiAgIHR1bmU6OmZpdF9yZXNhbXBsZXMoDQogICAgIHJlc2FtcGxlcyA9IGN2X2ZvbGRzLA0KICAgICBtZXRyaWNzID0geWFyZHN0aWNrOjptZXRyaWNfc2V0KGFjY3VyYWN5LCByb2NfYXVjKSwNCiAgICAgY29udHJvbCA9IGNvbnRyb2xfcmVzYW1wbGVzKHNhdmVfcHJlZCA9IFRSVUUpDQogICAgICkNCg0KIHJmX3JlcyAlPiUgIGNvbGxlY3RfbWV0cmljcyhzdW1tYXJpemUgPSBUUlVFKQ0KYGBgDQoNCg0KYGBge3IgUkYgRml0fQ0KDQpzZXQuc2VlZCgxMDEzKQ0KDQpyZl9maXQgPC0gZml0KHJmX3dvcmtmbG93LCBkYXRhID0gdHJhaW5fZGF0YSkNCg0KcmZfZml0DQpgYGANCg0KUHVsbGluZyB0aGUgY29uZnVzaW9uIG1hdHJpeCBvdXQgb2YgdGhlIG91dHB1dDogDQoNCipPT0IgZXN0aW1hdGUgb2YgIGVycm9yIHJhdGU6IDAuNDYlKg0KDQoqQ29uZnVzaW9uIG1hdHJpeDoqDQogICAgIDAgICAgMSBjbGFzcy5lcnJvciANCjAgNTcxMyAgIDEzIDAuMDAyMjcwMzQ2DQoxICAgNDAgNTY4NiAwLjAwNjk4NTY3OQ0KDQoNCmBgYHtyIFJGIFJlc2FtcGxlc30NCnNldC5zZWVkKDEwMTMpDQoNCnJmX2ZpdF9ycyA8LSByZl93b3JrZmxvdyAlPiUNCiAgZml0X3Jlc2FtcGxlcyhjdl9mb2xkcykNCg0KcmZfZml0X3JzICU+JSAgY29sbGVjdF9tZXRyaWNzKHN1bW1hcml6ZSA9IFRSVUUpDQoNCiNUSU1FRCBPVVQgIkVycm9yIGluIHBhc3RlMChzdHIxLCAiICIpIDogcmVhY2hlZCBlbGFwc2VkIHRpbWUgbGltaXQiDQoNCmBgYA0KDQoNCmBgYHtyIFJGIEVycm9yIC0gUk1TRX0NCmF1Z21lbnQocmZfZml0LCBuZXdfZGF0YSA9IHRlc3RfZGF0YSkgJT4lDQogIHJtc2UodHJ1dGggPSBhcy5udW1lcmljKGxlZnQpLCBlc3RpbWF0ZSA9IGFzLm51bWVyaWMoLnByZWRfY2xhc3MpKQ0KYGBgDQoNCmBgYHtyIFJGIFByZWRpY3Rpb25zfQ0KcmZfdGVzdGluZ19wcmVkIDwtIA0KICBwcmVkaWN0KHJmX2ZpdCwgdHJhaW5fZGF0YSkgJT4lIA0KICBiaW5kX2NvbHMocHJlZGljdChyZl9maXQsIHRyYWluX2RhdGEsIHR5cGUgPSAicHJvYiIpKSAlPiUgDQogIGJpbmRfY29scyh0cmFpbl9kYXRhICU+JSBzZWxlY3QobGVmdCkpDQoNCnJmX3Rlc3RpbmdfcHJlZA0KYGBgDQoNCmBgYHtyUkYgUk9DIFBsb3R9DQoNCnJmLnJvYzwtcm9jKHRyYWluX2RhdGEkbGVmdCwgcmZfZmluYWwkdm90ZXNbLDJdKQ0KcGxvdChyZi5yb2MpDQpwUk9DOjphdWMocmYucm9jKQ0KYGBgDQoNCg0KDQpgYGB7ciBSRiBBY2N1cmFjeX0NCnJmX3Rlc3RpbmdfcHJlZCAlPiUgIyB0ZXN0IHNldCBwcmVkaWN0aW9ucw0KICByb2NfYXVjKHRydXRoID0gbGVmdCwgLnByZWRfMSkNCg0KcmZfdGVzdGluZ19wcmVkICU+JSAgICAgICAgICAgICAgICAgICAjIHRlc3Qgc2V0IHByZWRpY3Rpb25zDQogIGFjY3VyYWN5KHRydXRoID0gbGVmdCwgLnByZWRfY2xhc3MpDQpgYGANCg0KDQpgYGB7ciBSRiBGaW5hbCBGaXR9DQpsYXN0X2ZpdF9yZiA8LSBsYXN0X2ZpdChyZl93b3JrZmxvdywgDQogICAgICAgICAgICAgICAgICAgICAgICBzcGxpdCA9IGRhdGFfc3BsaXQsDQogICAgICAgICAgICAgICAgICAgICAgICBtZXRyaWNzID0gbWV0cmljX3NldCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjYWxsLCBwcmVjaXNpb24sIGZfbWVhcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGFjY3VyYWN5LCBrYXAsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHJvY19hdWMsIHNlbnMsIHNwZWMpDQogICAgICAgICAgICAgICAgICAgICAgICApDQojU2hvdyBwZXJmb3JtYW5jZSBtZXRyaWNzDQoNCmxhc3RfZml0X3JmICU+JSANCiAgY29sbGVjdF9tZXRyaWNzKCkNCmBgYA0KDQpgYGB7ciBSRiBGaW5hbCBNb2RlbH0NCiNSRg0KcmZfZmluYWwgPC0gcmFuZG9tRm9yZXN0KGxlZnQgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSwgbXRyeSA9IDUsIHRyZWVzID0gMTAwMDAsIG1pbl9uID0gNCwga2VlcC5mb3Jlc3QgPSBUUlVFLCBpbXBvcnRhbmNlID0gVFJVRSkNCg0KcmZfZmluYWwNCiMgcmZfZmluYWxfZml0IDwtDQojICAgcmZfZmluYWwgJT4lDQojICAgZml0KGxlZnQgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSkNCmBgYA0KDQpgYGB7ciBSRiBWYXJpYWJsZSBJbXBvcnRhbmNlfQ0KI2xpYnJhcnkocmFuZG9tRm9yZXN0KQ0KDQppbXBvcnRhbmNlKHJmX2ZpbmFsLCBzb3J0ID0gVFJVRSkNCmBgYA0KTnVtYmVyIG9mIHByb2plY3RzIGFuZCBTYXRpc2ZhY3Rpb24gbGV2ZWwgYXJlIHN0cm9uZyBwcmVkaWN0b3JzIGluIHRoaXMgbW9kZWwuIA0KDQpgYGB7ciBSRiBWYXJpYWJsZXMgVXNlZH0NCnZhclVzZWQocmZfZmluYWwsIGNvdW50ID0gVFJVRSwgYnkudHJlZSA9IEZBTFNFKQ0KYGBgDQoNCmBgYHtyIFJGIEVycm9yIFBsb3R9DQoNCnBsb3QocmZfZmluYWwpDQoNCmBgYA0KDQoNCg0KDQpgYGB7ciBSRiBUcmVlfQ0KZ2V0VHJlZShyZl9maW5hbCwgaz0xLCBsYWJlbFZhciA9IFRSVUUpDQpgYGANCg0KDQoNCiMjIDQgTmV1cmFsIE5ldA0KDQpgYGB7ciBOTiBXb3JrZmxvd30NCiNMT0dJU1RJQyBSRUdSRVNTSU9OIFdPUktGTE9XDQpubl93b3JrZmxvdyA8LSB3b3JrZmxvdygpICU+JQ0KICBhZGRfcmVjaXBlKHJlY2lwZV8xKSAlPiUgDQogIGFkZF9tb2RlbChzcGVjX25uZXQpDQoNCm5uX3dvcmtmbG93DQpgYGANCg0KDQpgYGB7ciBOTiBSZXN9DQpzZXQuc2VlZCgxMDEzKQ0KDQpubmV0X3JlcyA8LSANCiAgbm5fd29ya2Zsb3cgJT4lIA0KICBmaXRfcmVzYW1wbGVzKA0KICAgIHJlc2FtcGxlcyA9IGN2X2ZvbGRzLCANCiAgICBtZXRyaWNzID0gbWV0cmljX3NldCgNCiAgICAgIHJlY2FsbCwgcHJlY2lzaW9uLCBmX21lYXMsIA0KICAgICAgYWNjdXJhY3ksIGthcCwNCiAgICAgIHJvY19hdWMsIHNlbnMsIHNwZWMpLA0KICAgIGNvbnRyb2wgPSBjb250cm9sX3Jlc2FtcGxlcyhzYXZlX3ByZWQgPSBUUlVFKQ0KICAgICkgDQoNCm5uZXRfcmVzDQpgYGANCg0KDQpgYGB7ciBOTiBWYXJpYWJsZSBJbXBvcnRhbmNlIE1hcH0NCmxpYnJhcnkoTmV1cmFsTmV0VG9vbHMpDQojaHR0cHM6Ly9mYXdkYTEyMy5naXRodWIuaW8vTmV1cmFsTmV0VG9vbHMvYXJ0aWNsZXMvT3ZlcnZpZXcuaHRtbA0KDQpubl9tb2RlbCA8LSBubmV0OjpubmV0KGxlZnQgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSwgc2l6ZSA9IDEwKQ0KDQpwYXIobWFyID0gbnVtZXJpYyg0KSkNCnBsb3RuZXQobm5fbW9kZWwpDQoNCiMgVGhlIHBsb3RuZXQgZnVuY3Rpb24gcGxvdHMgYSBuZXVyYWwgbmV0d29yayBhcyBhIHNpbXBsZSBuZXR3b3JrIG9yIGFzIGEgbmV1cmFsIGludGVycHJldGF0aW9uIGRpYWdyYW0gKE5JRCkuIFRoZSBkZWZhdWx0IHNldHRpbmdzIGFyZSB0byBwbG90IGFzIE5JRCB3aXRoIHBvc2l0aXZlIHdlaWdodHMgYmV0d2VlbiBsYXllcnMgYXMgYmxhY2sgbGluZXMgYW5kIG5lZ2F0aXZlIHdlaWdodHMgYXMgZ3JleSBsaW5lcy4gTGluZSB0aGlja25lc3MgaXMgaW4gcHJvcG9ydGlvbiB0byByZWxhdGl2ZSBtYWduaXR1ZGUgb2YgZWFjaCB3ZWlnaHQuIFRoZSBmaXJzdCBsYXllciBpbmNsdWRlcyBvbmx5IGlucHV0IHZhcmlhYmxlcyB3aXRoIG5vZGVzIGxhYmVsbGVkIGFzIEkxIHRocm91Z2ggSW4gZm9yIG4gaW5wdXQgdmFyaWFibGVzLiBPbmUgdGhyb3VnaCBtYW55IGhpZGRlbiBsYXllcnMgYXJlIHBsb3R0ZWQgd2l0aCBlYWNoIG5vZGUgaW4gZWFjaCBsYXllciBsYWJlbGxlZCBhcyBIMSB0aHJvdWdoIEhuLiBUaGUgb3V0cHV0IGxheWVyIGlzIHBsb3R0ZWQgbGFzdCB3aXRoIG5vZGVzIGxhYmVsZWQgYXMgTzEgdGhyb3VnaCBPbi4gQmlhcyBub2RlcyBjb25uZWN0ZWQgdG8gdGhlIGhpZGRlbiBhbmQgb3V0cHV0IGxheWVycyBhcmUgYWxzbyBzaG93bi4NCg0KYGBgDQoNCg0KYGBge3IgTk4gVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90c30NCmdhcnNvbihubl9tb2RlbCkNCm9sZGVuKG5uX21vZGVsKQ0KDQojIFRoZSBnYXJzb24gZnVuY3Rpb24gdXNlcyBHYXJzb27igJlzIGFsZ29yaXRobSB0byBldmFsdWF0ZSByZWxhdGl2ZSB2YXJpYWJsZSBpbXBvcnRhbmNlLiBUaGlzIGZ1bmN0aW9uIGlkZW50aWZpZXMgdGhlIHJlbGF0aXZlIGltcG9ydGFuY2Ugb2YgZXhwbGFuYXRvcnkgdmFyaWFibGVzIGZvciBhIHNpbmdsZSByZXNwb25zZSB2YXJpYWJsZSBieSBkZWNvbnN0cnVjdGluZyB0aGUgbW9kZWwgd2VpZ2h0cy4gVGhlIGltcG9ydGFuY2Ugb2YgZWFjaCB2YXJpYWJsZSBjYW4gYmUgZGV0ZXJtaW5lZCBieSBpZGVudGlmeWluZyBhbGwgd2VpZ2h0ZWQgY29ubmVjdGlvbnMgYmV0d2VlbiB0aGUgbGF5ZXJzIGluIHRoZSBuZXR3b3JrLiBUaGF0IGlzLCBhbGwgd2VpZ2h0cyBjb25uZWN0aW5nIHRoZSBzcGVjaWZpYyBpbnB1dCBub2RlIHRoYXQgcGFzcyB0aHJvdWdoIHRoZSBoaWRkZW4gbGF5ZXIgdG8gdGhlIHJlc3BvbnNlIHZhcmlhYmxlIGFyZSBpZGVudGlmaWVkLiBUaGlzIGlzIHJlcGVhdGVkIGZvciBhbGwgb3RoZXIgZXhwbGFuYXRvcnkgdmFyaWFibGVzIHVudGlsIGEgbGlzdCBvZiBhbGwgd2VpZ2h0cyB0aGF0IGFyZSBzcGVjaWZpYyB0byBlYWNoIGlucHV0IHZhcmlhYmxlIGlzIG9idGFpbmVkLiBUaGUgY29ubmVjdGlvbnMgYXJlIHRhbGxpZWQgZm9yIGVhY2ggaW5wdXQgbm9kZSBhbmQgc2NhbGVkIHJlbGF0aXZlIHRvIGFsbCBvdGhlciBpbnB1dHMuIEEgc2luZ2xlIHZhbHVlIGlzIG9idGFpbmVkIGZvciBlYWNoIGV4cGxhbmF0b3J5IHZhcmlhYmxlIHRoYXQgZGVzY3JpYmVzIHRoZSByZWxhdGlvbnNoaXAgd2l0aCB0aGUgcmVzcG9uc2UgdmFyaWFibGUgaW4gdGhlIG1vZGVsLiBUaGUgcmVzdWx0cyBpbmRpY2F0ZSByZWxhdGl2ZSBpbXBvcnRhbmNlIGFzIHRoZSBhYnNvbHV0ZSBtYWduaXR1ZGUgZnJvbSB6ZXJvIHRvIG9uZS4gVGhlIGZ1bmN0aW9uIGNhbm5vdCBiZSB1c2VkIHRvIGV2YWx1YXRlIHRoZSBkaXJlY3Rpb24gb2YgdGhlIHJlc3BvbnNlLiBPbmx5IG5ldXJhbCBuZXR3b3JrcyB3aXRoIG9uZSBoaWRkZW4gbGF5ZXIgYW5kIG9uZSBvdXRwdXQgbm9kZSBjYW4gYmUgZXZhbHVhdGVkLg0KDQojIFRoZSBvbGRlbiBmdW5jdGlvbiBpcyBhbiBhbHRlcm5hdGl2ZSBhbmQgbW9yZSBmbGV4aWJsZSBhcHByb2FjaCB0byBldmFsdWF0ZSB2YXJpYWJsZSBpbXBvcnRhbmNlLiBUaGUgZnVuY3Rpb24gY2FsY3VsYXRlcyBpcG9ydGFuY2UgYXMgdGhlIHByb2R1Y3Qgb2YgdGhlIHJhdyBpbnB1dC1oaWRkZW4gYW5kIGhpZGRlbi1vdXRwdXQgY29ubmVjdGlvbiB3ZWlnaHRzIGJldHdlZW4gZWFjaCBpbnB1dCBhbmQgb3V0cHV0IG5ldXJvbiBhbmQgc3VtcyB0aGUgcHJvZHVjdCBhY3Jvc3MgYWxsIGhpZGRlbiBuZXVyb25zLiBBbiBhZHZhbnRhZ2Ugb2YgdGhpcyBhcHByb2FjaCBpcyB0aGUgcmVsYXRpdmUgY29udHJpYnV0aW9ucyBvZiBlYWNoIGNvbm5lY3Rpb24gd2VpZ2h0IGFyZSBtYWludGFpbmVkIGluIHRlcm1zIG9mIGJvdGggbWFnbml0dWRlIGFuZCBzaWduIGFzIGNvbXBhcmVkIHRvIEdhcnNvbuKAmXMgYWxnb3JpdGhtIHdoaWNoIG9ubHkgY29uc2lkZXJzIHRoZSBhYnNvbHV0ZSBtYWduaXR1ZGUuIEZvciBleGFtcGxlLCBjb25uZWN0aW9uIHdlaWdodHMgdGhhdCBjaGFuZ2Ugc2lnbiAoZS5nLiwgcG9zaXRpdmUgdG8gbmVnYXRpdmUpIGJldHdlZW4gdGhlIGlucHV0LWhpZGRlbiB0byBoaWRkZW4tb3V0cHV0IGxheWVycyB3b3VsZCBoYXZlIGEgY2FuY2VsbGluZyBlZmZlY3Qgd2hlcmVhcyBHYXJzb27igJlzIGFsZ29yaXRobSBtYXkgcHJvdmlkZSBtaXNsZWFkaW5nIHJlc3VsdHMgYmFzZWQgb24gdGhlIGFic29sdXRlIG1hZ25pdHVkZS4gQW4gYWRkaXRpb25hbCBhZHZhbnRhZ2UgaXMgdGhhdCBPbGRlbuKAmXMgYWxnb3JpdGhtIGlzIGNhcGFibGUgb2YgZXZhbHVhdGluZyBuZXVyYWwgbmV0d29ya3Mgd2l0aCBtdWx0aXBsZSBoaWRkZW4gbGF5ZXJzIGFuZCByZXNwb25zZSB2YXJpYWJsZXMuIFRoZSBpbXBvcnRhbmNlIHZhbHVlcyBhc3NpZ25lZCB0byBlYWNoIHZhcmlhYmxlIGFyZSBpbiB1bml0cyB0aGF0IGFyZSBiYXNlZCBkaXJlY3RseSBvbiB0aGUgc3VtbWVkIHByb2R1Y3Qgb2YgdGhlIGNvbm5lY3Rpb24gd2VpZ2h0cy4gVGhlIGFjdHVhbCB2YWx1ZXMgc2hvdWxkIG9ubHkgYmUgaW50ZXJwcmV0ZWQgYmFzZWQgb24gcmVsYXRpdmUgc2lnbiBhbmQgbWFnbml0dWRlIGJldHdlZW4gZXhwbGFuYXRvcnkgdmFyaWFibGVzLiBDb21wYXJpc29ucyBiZXR3ZWVuIGRpZmZlcmVudCBtb2RlbHMgc2hvdWxkIG5vdCBiZSBtYWRlLg0KYGBgDQoNCg0KDQpgYGB7ciBOTiBGaW5hbCBGaXR9DQpsYXN0X2ZpdF9ubiA8LSBsYXN0X2ZpdChubl93b3JrZmxvdywgDQogICAgICAgICAgICAgICAgICAgICAgICBzcGxpdCA9IGRhdGFfc3BsaXQsDQogICAgICAgICAgICAgICAgICAgICAgICBtZXRyaWNzID0gbWV0cmljX3NldCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjYWxsLCBwcmVjaXNpb24sIGZfbWVhcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGFjY3VyYWN5LCBrYXAsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHJvY19hdWMsIHNlbnMsIHNwZWMpDQogICAgICAgICAgICAgICAgICAgICAgICApDQojU2hvdyBwZXJmb3JtYW5jZSBtZXRyaWNzDQoNCmxhc3RfZml0X25uICU+JSANCiAgY29sbGVjdF9tZXRyaWNzKCkNCmBgYA0KDQpUaGVzZSBhcmUgb3VyIGZpbmFsIHBlcmZvcm1hbmNlIG1ldHJpY3MuIFRoZSBtb2RlbCBwZXJmb3JtcyB3ZWxsIGJvdGggb24gdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEuIFdlIGhhdmUgc29tZSBpbnNpZ2h0IGludG8gdGhlIHZhcmlhYmxlcyBkcml2aW5nIG91ciBtb2RlbCwgaW5jbHVkaW5nIEF2ZXJhZ2UgTW9udGhseSBIb3VycyBhbmQgTnVtYmVyIG9mIHByb2plY3RzLiANCg0KDQpgYGB7ciBOTiBST0MgUGxvdH0NCmxhc3RfZml0X25uICU+JSANCiAgY29sbGVjdF9wcmVkaWN0aW9ucygpICU+JSANCiAgcm9jX2N1cnZlKGxlZnQsIC5wcmVkXzApICU+JSANCiAgYXV0b3Bsb3QoKQ0KYGBgDQoNCmBgYHtyIE5OIENvbmZ1c2lvbiBNYXRyaXh9DQoNCmxhc3RfZml0X25uICU+JSANCiAgY29sbGVjdF9wcmVkaWN0aW9ucygpICU+JSANCiAgY29uZl9tYXQodHJ1dGggPSBsZWZ0LCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQ0KDQpgYGANClJFU1VMVHM6DQoqIDcxLjYlIChuPTE3OTEpIFByZWRpY3Rpb24gd2FzIE5vIEF0dHJpdGlvbiwgVHJ1dGggd2FzIE5vIEF0dHJpdGlvbg0KKiAyMS44JSAobj01NDUpIFByZWRpY3Rpb24gd2FzIEF0dHJpdGlvbiwgVHJ1dGggd2FzIEF0dHJpdGlvbg0KLS0NCjkzLjQlIEFjY3VyYXRlIHByZWRpY3Rpb24NCg0KKiA0LjclIChuPTExOCkgUHJlZGljdGlvbiB3YXMgQXR0cml0aW9uLCBUcnV0aCB3YXMgTm8gQXR0cml0aW9uDQoqIDEuOCUgKG49NDYpIFByZWRpY3Rpb24gd2FzIE5vIEF0dHJpdGlvbiwgVHJ1dGggd2FzIEF0dHJpdGlvbg0KLS0NCjYuNSUgSW5hY2N1cmF0ZSBwcmVkaWN0aW9uDQoNClRoaXMgaXMgYSBtdWNoIGJldHRlciBhY2N1cmFjeSB0aGFuIGVpdGhlciB0aGUgU1ZNIE9yIExvZ2lzdGljIFJlZ3Jlc3Npb24gbW9kZWxzIGluIGFsbCBkaXJlY3Rpb25zLiBIb3dldmVyLCBpbnRlcnByZXRhYmlsaXR5IG1heSBiZSB0b28gZGlmZmljdWx0IHRvIG1ha2UgaXQgdXNlZnVsLiANCg0KDQojIENvbXBhcmUgTW9kZWxzDQoNCmBgYHtyIE1vZGVsIENvbXBhcmlzb24gTWV0cmljc30NCiNodHRwczovL3d3dy5raXJlbnouY29tL3Bvc3QvMjAyMS0wMi0xNy1yLWNsYXNzaWZpY2F0aW9uLXRpZHltb2RlbHMvI2xhc3QtZXZhbHVhdGlvbi1vbi10ZXN0LXNldA0KDQpsb2dfbWV0cmljcyA8LSANCiAgbG9nX3JlcyAlPiUgDQogIGNvbGxlY3RfbWV0cmljcyhzdW1tYXJpc2UgPSBUUlVFKSAlPiUNCiAgbXV0YXRlKG1vZGVsID0gIkxvZ2lzdGljIFJlZ3Jlc3Npb24iKSANCg0KcmZfbWV0cmljcyA8LQ0KICByZl9yZXMgJT4lDQogIGNvbGxlY3RfbWV0cmljcyhzdW1tYXJpc2UgPSBUUlVFKSAlPiUNCiAgbXV0YXRlKG1vZGVsID0gIlJhbmRvbSBGb3Jlc3QiKSAjU2VlbSB0byBiZSBydW5uaW5nIGludG8gYSBXaW5kb3dzIGVycm9yIC8ga25vd24gYnVnIGluIHBhY2thZ2UgcHJldmVudGluZyBhYmlsaXR5IHRvIGdldCB0aGVzZSBtZXRyaWNzIGZvciBSYW5kb20gRm9yZXN0IG1vZGVsDQoNCnN2bV9tZXRyaWNzIDwtIA0KICBzdm1fcmVzICU+JSANCiAgY29sbGVjdF9tZXRyaWNzKHN1bW1hcmlzZSA9IFRSVUUpICU+JQ0KICBtdXRhdGUobW9kZWwgPSAiU3VwcG9ydCBWZWN0b3IgQ2xhc3NpZmllciIpDQoNCm5uZXRfbWV0cmljcyA8LQ0KICBubmV0X3JlcyAlPiUNCiAgY29sbGVjdF9tZXRyaWNzKHN1bW1hcmlzZSA9IFRSVUUpICU+JQ0KICBtdXRhdGUobW9kZWwgPSAiTmV1cmFsIE5ldCIpDQoNCiMgY3JlYXRlIGRhdGFmcmFtZSB3aXRoIGFsbCBtb2RlbHMNCm1vZGVsX2NvbXBhcmUgPC0gYmluZF9yb3dzKA0KICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dfbWV0cmljcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJmX21ldHJpY3MsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzdm1fbWV0cmljcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5uZXRfbWV0cmljcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCg0KIyBjaGFuZ2UgZGF0YSBzdHJ1Y3R1cmUNCm1vZGVsX2NvbXAgPC0gDQogIG1vZGVsX2NvbXBhcmUgJT4lIA0KICBzZWxlY3QobW9kZWwsIC5tZXRyaWMsIG1lYW4sIHN0ZF9lcnIpICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IC5tZXRyaWMsIHZhbHVlc19mcm9tID0gYyhtZWFuLCBzdGRfZXJyKSkgDQpgYGANCg0KDQpgYGAge3IgTW9kZWwgQ29tcGFyaXNvbiAtIEFjY3VyYWN5fQ0KIyBzaG93IG1lYW4gQWNjdXJhY3ktU2NvcmUgZm9yIGV2ZXJ5IG1vZGVsDQptb2RlbF9jb21wICU+JSANCiAgYXJyYW5nZShtZWFuX2FjY3VyYWN5KSAlPiUgDQogIG11dGF0ZShtb2RlbCA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKG1vZGVsLCBtZWFuX2FjY3VyYWN5KSkgJT4lICMgb3JkZXIgcmVzdWx0cw0KICBnZ3Bsb3QoYWVzKG1vZGVsLCBtZWFuX2FjY3VyYWN5LCBmaWxsPW1vZGVsKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIpICsNCiAgIGdlb21fdGV4dCgNCiAgICAgc2l6ZSA9IDMsDQogICAgIGFlcyhsYWJlbCA9IHJvdW5kKG1lYW5fZl9tZWFzLCAyKSwgeSA9IG1lYW5fZl9tZWFzICsgMC4wOCksDQogICAgIHZqdXN0ID0gMQ0KICApDQpgYGANCg0KDQpgYGB7ciBNb2RlbCBDb21wYXJpc29uIC0gQVVDfQ0KIyBzaG93IG1lYW4gYXJlYSB1bmRlciB0aGUgY3VydmUgKGF1YykgcGVyIG1vZGVsDQptb2RlbF9jb21wICU+JSANCiAgYXJyYW5nZShtZWFuX3JvY19hdWMpICU+JSANCiAgbXV0YXRlKG1vZGVsID0gZm9yY2F0czo6ZmN0X3Jlb3JkZXIobW9kZWwsIG1lYW5fcm9jX2F1YykpICU+JQ0KICBnZ3Bsb3QoYWVzKG1vZGVsLCBtZWFuX3JvY19hdWMsIGZpbGw9bW9kZWwpKSArDQogIGdlb21fY29sKCkgKw0KICBjb29yZF9mbGlwKCkgKw0KICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJsdWVzIikgKyANCiAgICAgZ2VvbV90ZXh0KA0KICAgICBzaXplID0gMywNCiAgICAgYWVzKGxhYmVsID0gcm91bmQobWVhbl9yb2NfYXVjLCAyKSwgeSA9IG1lYW5fcm9jX2F1YyArIDAuMDgpLA0KICAgICB2anVzdCA9IDENCiAgKQ0KYGBgDQoNCiMjIyBUYWJsZSAxDQoNCkZlYXR1cmUgQ29lZmZpY2llbnRzDQoNCmBgYHtyIFByZWRpY3RvciBWYXJpYWJsZSBDb2VmZmljaWVudHN9DQoNCg0KDQpgYGANCg0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KIyBBUFBFTkRJWCAtIENPTU1FTlRFRCBPVVQgTk9URVMgJiBUSE9VR0hUUyBGT1IgRlVUVVJFIFBST0pFQ1RTDQoNCiMgQnVzaW5lc3MgVmFsdWU6IFZpc3VhbGl6aW5nIE1vZGVsIFBlcmZvcm1hbmNlDQoNClR1cm5zIG91dCB0aGVyZSBpcyBhIGJ1ZyBpbiB0aGlzIHBhY2thZ2Ugc28gd2UgY2FuJ3QgbWFrZSBhbGwgdGhlIGZhbmN5IGNoYXJ0cy4gd2FoIHdhaA0KDQpgYGB7ciBGaW5hbCBNb2RlbCBPYmplY3RzfQ0KDQojRlJPTTogaHR0cHM6Ly93d3cua2RudWdnZXRzLmNvbS8yMDE5LzA2L21vZGVscGxvdHItY3Jhbi1idXNpbmVzcy12YWx1ZS1wcmVkaWN0aXZlLW1vZGVscy5odG1sDQoNCg0KIyBzZXQuc2VlZCgxMDEzKQ0KIyANCiMgI0xPR0lUDQojIGxvZ2l0X2ZpbmFsIDwtIGxvZ2lzdGljX3JlZyhsZWZ0IH4gLiwgbW9kZSA9ICJjbGFzc2lmaWNhdGlvbiIsIGVuZ2luZSA9ICJnbG0iKQ0KIyANCiMgbG9naXRfZmluYWxfZml0IDwtIA0KIyAgIGxvZ2l0X2ZpbmFsICU+JSANCiMgICBmaXQobGVmdCB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhKQ0KIyANCiMgI1JGDQojIHJmX2ZpbmFsIDwtIHJhbmRvbUZvcmVzdChsZWZ0IH4gLiwgZGF0YSA9IHRyYWluX2RhdGEsIG10cnkgPSA1LCB0cmVlcyA9IDEwMDAwLCBtaW5fbiA9IDQsIGtlZXAuZm9yZXN0ID0gRkFMU0UsIGltcG9ydGFuY2UgPSBUUlVFKSANCiMgDQojICMgcmZfZmluYWxfZml0IDwtIA0KIyAjICAgcmZfZmluYWwgJT4lIA0KIyAjICAgZml0KGxlZnQgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSkNCiMgDQojICNOTg0KIyBubl9maW5hbCA8LSBtbHAobGVmdCB+IC4sIG1vZGUgPSAiY2xhc3NpZmljYXRpb24iLCBlbmdpbmUgPSAia2VyYXMiKQ0KIyANCiMgIyBubl9maW5hbF9maXQgPC0gDQojICMgICBubl9maW5hbCAlPiUgDQojICMgICBmaXQobGVmdCB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhKQ0KIyANCiMgI1NWTQ0KIyBzdm1fZmluYWwgPC0gc3ZtX2xpbmVhcihsZWZ0IH4gLiwgbW9kZSA9ICJjbGFzc2lmaWNhdGlvbiIsIGVuZ2luZSA9ICJrZXJubGFiIiwgY29zdCA9IDEpDQojIA0KIyBzdm1fZmluYWxfZml0IDwtIA0KIyAgIHN2bV9maW5hbCAlPiUgDQojICAgZml0KGxlZnQgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSkNCg0KYGBgDQoNCg0KDQpgYGB7ciBGaW5hbCBNb2RlbCBSZXN1bHRzIFRhYmxlfQ0KI2xpYnJhcnkobW9kZWxwbG90cikNCg0KIyB0cmFuc2Zvcm0gZGF0YXNldHMgYW5kIG1vZGVsIG9iamVjdHMgaW50byBzY29yZWQgZGF0YSBhbmQgY2FsY3VsYXRlIG50aWxlcyANCiMgcHJlcGFyYXRpb24gc3RlcHMNCiMgc2NvcmVzX2FuZF9udGlsZXMgPC0gcHJlcGFyZV9zY29yZXNfYW5kX250aWxlcyhkYXRhc2V0cyA9IGxpc3QoInRyYWluX2RhdGEiLCJ0ZXN0X2RhdGEiKSwNCiMgICAgICAgICAgICAgICAgICAgICAgIGRhdGFzZXRfbGFiZWxzID0gbGlzdCgidHJhaW4gZGF0YSIsInRlc3QgZGF0YSIpLA0KIyAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxzID0gbGlzdCgibG9naXRfZmluYWxfZml0IiwicmZfZmluYWxfZml0IiwgIm5uX2ZpbmFsIiwgInN2bV9maW5hbF9maXQiKSwgDQojICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9sYWJlbHMgPSBsaXN0KCJMb2dpc3RpYyBSZWdyZXNzaW9uIiwiUmFuZG9tIEZvcmVzdCIsICJOZXVyYWwgTmV0IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlN1cHBvcnQgVmVjdG9yIENsYXNzaWZpZXIiKSwgIA0KIyAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0X2NvbHVtbj0ibGVmdCIsDQojICAgICAgICAgICAgICAgICAgICAgICBudGlsZXM9MTAwKQ0KDQojRXJyb3I6IERpZCB5b3UgbWVhbiB0byB1c2UgYG5ld19kYXRhYCBpbnN0ZWFkIG9mIGBuZXdkYXRhYD8gLS0gaW5zdXJtb3VudGFibGUgYnVnDQpgYGANCg0KDQoNCmBgYHtyIEZpbmFsIE1vZGVsIFZpeiBJbnB1dHN9DQojIHRyYW5zZm9ybSBkYXRhIGdlbmVyYXRlZCB3aXRoIHByZXBhcmVfc2NvcmVzX2FuZF9udGlsZXMgaW50byBhZ2dyZWdhdGVkIGRhdGEgZm9yIGNob3NlbiBwbG90dGluZyBzY29wZSANCg0KIyBwbG90X2lucHV0X2xvZyA8LSBwbG90dGluZ19zY29wZShwcmVwYXJlZF9pbnB1dCA9IHNjb3Jlc19hbmRfbnRpbGVzLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdF9tb2RlbF9sYWJlbCA9ICJMb2dpc3RpYyBSZWdyZXNzaW9uIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RfZGF0YXNldF9sYWJlbCA9ICJ0ZXN0IGRhdGEiKQ0KIyANCiMgcGxvdF9pbnB1dF9yZiA8LSBwbG90dGluZ19zY29wZShwcmVwYXJlZF9pbnB1dCA9IHNjb3Jlc19hbmRfbnRpbGVzLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdF9tb2RlbF9sYWJlbCA9ICJSYW5kb20gRm9yZXN0IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RfZGF0YXNldF9sYWJlbCA9ICJ0ZXN0IGRhdGEiKQ0KIyANCiMgcGxvdF9pbnB1dF9ubiA8LSBwbG90dGluZ19zY29wZShwcmVwYXJlZF9pbnB1dCA9IHNjb3Jlc19hbmRfbnRpbGVzLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdF9tb2RlbF9sYWJlbCA9ICJOZXVyYWwgTmV0IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RfZGF0YXNldF9sYWJlbCA9ICJ0ZXN0IGRhdGEiKQ0KIyANCiMgcGxvdF9pbnB1dF9zdm0gPC0gcGxvdHRpbmdfc2NvcGUocHJlcGFyZWRfaW5wdXQgPSBzY29yZXNfYW5kX250aWxlcywNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RfbW9kZWxfbGFiZWwgPSAiU3VwcG9ydCBWZWN0b3IgQ2xhc3NpZmllciIsDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0X2RhdGFzZXRfbGFiZWwgPSAidGVzdCBkYXRhIikNCg0KYGBgDQoNCg0KDQpgYGB7ciBGaW5hbCBNb2RlbCBQbG90fQ0KIyBwbG90IGFsbCBmb3VyIGV2YWx1YXRpb24gcGxvdHMgYW5kIHNhdmUgdG8gZmlsZSwgaGlnaGxpZ2h0IGRlY2lsZSAyDQojIHBsb3RfbXVsdGlwbG90KGRhdGEgPSBwbG90X2lucHV0X2xvZywgaGlnaGxpZ2h0X250aWxlPTIsDQojICAgICAgICAgICAgICAgIHNhdmVfZmlnID0gVFJVRSwgc2F2ZV9maWdfZmlsZW5hbWUgPSAnUHJlZGljdGl2ZSBBdHRyaXRpb24gTW9kZWwnKQ0KIyANCiMgcGxvdF9tdWx0aXBsb3QoZGF0YSA9IHBsb3RfaW5wdXRfcmYsIGhpZ2hsaWdodF9udGlsZT0yLA0KIyAgICAgICAgICAgICAgICBzYXZlX2ZpZyA9IFRSVUUsIHNhdmVfZmlnX2ZpbGVuYW1lID0gJ1ByZWRpY3RpdmUgQXR0cml0aW9uIE1vZGVsJykNCiMgDQojIHBsb3RfbXVsdGlwbG90KGRhdGEgPSBwbG90X2lucHV0X25uLCBoaWdobGlnaHRfbnRpbGU9MiwNCiMgICAgICAgICAgICAgICAgc2F2ZV9maWcgPSBUUlVFLCBzYXZlX2ZpZ19maWxlbmFtZSA9ICdQcmVkaWN0aXZlIEF0dHJpdGlvbiBNb2RlbCcpDQojIA0KIyBwbG90X211bHRpcGxvdChkYXRhID0gcGxvdF9pbnB1dF9zdm0sIGhpZ2hsaWdodF9udGlsZT0yLA0KIyAgICAgICAgICAgICAgICBzYXZlX2ZpZyA9IFRSVUUsIHNhdmVfZmlnX2ZpbGVuYW1lID0gJ1ByZWRpY3RpdmUgQXR0cml0aW9uIE1vZGVsJykNCg0KYGBgDQoNCg0KDQpgYGB7ciBGaW5hbCBNb2RlbCBQbG90IC0gUk9JfQ0KI1Byb2ZpdCBwbG90IC0gYXV0b21hdGljYWxseSBoaWdobGlnaHRpbmcgdGhlIG50aWxlIHdoZXJlIHByb2ZpdCBpcyBtYXhpbWl6ZWQhDQojIHBsb3RfcHJvZml0KGRhdGEgPSBwbG90X2lucHV0X2xvZywgDQojICAgICAgICAgICAgIGZpeGVkX2Nvc3RzID0gMTAwMDAwLCANCiMgICAgICAgICAgICAgdmFyaWFibGVfY29zdHNfcGVyX3VuaXQgPSA1MDAwLA0KIyAgICAgICAgICAgICBwcm9maXRfcGVyX3VuaXQgPSA5NTAwMCkNCiMgDQojIHBsb3RfcHJvZml0KGRhdGEgPSBwbG90X2lucHV0X3JmLCANCiMgICAgICAgICAgICAgZml4ZWRfY29zdHMgPSAxMDAwMDAsIA0KIyAgICAgICAgICAgICB2YXJpYWJsZV9jb3N0c19wZXJfdW5pdCA9IDUwMDAsDQojICAgICAgICAgICAgIHByb2ZpdF9wZXJfdW5pdCA9IDk1MDAwKQ0KIyANCiMgcGxvdF9wcm9maXQoZGF0YSA9IHBsb3RfaW5wdXRfbm4sIA0KIyAgICAgICAgICAgICBmaXhlZF9jb3N0cyA9IDEwMDAwMCwgDQojICAgICAgICAgICAgIHZhcmlhYmxlX2Nvc3RzX3Blcl91bml0ID0gNTAwMCwNCiMgICAgICAgICAgICAgcHJvZml0X3Blcl91bml0ID0gOTUwMDApDQojIA0KIyBwbG90X3Byb2ZpdChkYXRhID0gcGxvdF9pbnB1dF9zdm0sIA0KIyAgICAgICAgICAgICBmaXhlZF9jb3N0cyA9IDEwMDAwMCwgDQojICAgICAgICAgICAgIHZhcmlhYmxlX2Nvc3RzX3Blcl91bml0ID0gNTAwMCwNCiMgICAgICAgICAgICAgcHJvZml0X3Blcl91bml0ID0gOTUwMDApDQogDQpgYGANCg0KDQojIyMgV09SS0ZMT1dTRVRTDQoNCjwhLS0gI0RJRCBOT1QgVVNFIFdPUktGTE9XIFNFVFMgRFVFIFRPIFNPTUUgS05PV04gQlVHUy4gQlVUIFRISVMgTUlHSFQgQkUgSEVMUEZVTCBGT1IgTEFURVI6IC0tPg0KPCEtLSAjIEl0ZXJhdGluZyB0aHJvdWdoIE1vZGVsICYgUmVjaXBlIENvbWJpbmF0aW9ucyAtLT4NCg0KPCEtLSAjR2VuZXJhdGUgTGlzdCBvZiBSZWNpcGVzIC0tPg0KPCEtLSByZWNpcGVfbGlzdCA8LSAgLS0+DQo8IS0tIGxpc3QoUmVjaXBlMSA9IHJlY2lwZV8xLCBSZWNpcGUyID0gcmVjaXBlXzIsIFJlY2lwZTMgPSByZWNpcGVfMywgUmVjaXBlNCA9IHJlY2lwZV80KSAtLT4NCg0KPCEtLSAjR2VuZXJhdGUgTGlzdCBvZiBNb2RlbCBUeXBlcyAtLT4NCjwhLS0gbW9kZWxfbGlzdCA8LSAgLS0+DQo8IS0tIGxpc3QoUmFuZG9tX0ZvcmVzdCA9IHJmX2xvYW4sIFNWTSA9IHN2bV9sb2FuLCBOYWl2ZV9CYXllcyA9IG5iX2xvYW4sIERlY2lzaW9uX1RyZWUgPSBkdF9sb2FuLCBCb29zdGVkX1RyZWVzID0geGdib29zdF9sb2FuLCBLTk4gPSBrbm5fbG9hbiwgTG9naXN0aWNfUmVncmVzc2lvbiA9IGxvZ2lzdGljX2xvYW4pIC0tPg0KDQo8IS0tICMgSGVyZSBjb21lcyB0aGUgZnVuIHBhcnQsIGNyZWF0aW5nIGEgc2VyaWVzIG9mIHdvcmtmbG93cyB1c2luZyB3b3JrZmxvd19zZXRzKCkgLS0+DQo8IS0tIG1vZGVsX3NldCA8LSB3b3JrZmxvd19zZXQocHJlcHJvYyA9IHJlY2lwZV9saXN0LCBtb2RlbHMgPSBtb2RlbF9saXN0LCBjcm9zcyA9IFQpIC0tPg0KDQo8IS0tIFNldHRpbmcgY3Jvc3MgPSBUIGluc3RydWN0cyB3b3JrZmxvd19zZXQgdG8gY3JlYXRlIGFsbCBwb3NzaWJsZSBjb21iaW5hdGlvbnMgb2YgcGFyc25pcCBtb2RlbCBhbmQgcmVjaXBlIHNwZWNpZmljYXRpb24uIC0tPg0KDQo8IS0tIHNldC5zZWVkKDIpIC0tPg0KPCEtLSB0cmFpbl9yZXNhbXBsZXMgPC0gYm9vdHN0cmFwcyh0cmFpbmluZyhsb2FuX3NwbGl0KSwgc3RyYXRhID0gTG9hbl9TdGF0dXMpIC0tPg0KPCEtLSBkb1BhcmFsbGVsOjpyZWdpc3RlckRvUGFyYWxsZWwoY29yZXMgPSAxMikgLS0+DQo8IS0tIGFsbF93b3JrZmxvd3MgPC0gIC0tPg0KPCEtLSAgIG1vZGVsX3NldCAlPiUgd29ya2Zsb3dfbWFwKHJlc2FtcGxlcyA9IHRyYWluX3Jlc2FtcGxlcywgIC0tPg0KPCEtLSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBUUlVFKSAtLT4NCg0KPCEtLSBXZeKAmXZlIGluaXRpYWxpc2VkIGEgYm9vc3RyYXBzIHJlc2FtcGxpbmcgcHJvY2VkdXJlLCB0aGlzIGlzIGNvbXB1dGF0aW9uYWxseSBtb3JlIGRlbWFuZGluZywgaG93ZXZlciByZXN1bHRzIGluIGxlc3MgZXJyb3Igb3ZlcmFsbC4gUGFzc2luZyB0aGUgbW9kZWxfc2V0IG9iamVjdCB0aHJvdWdoIHRvIHdvcmtmbG93X21hcCwgd2l0aCB0aGUgcmVzYW1wbGluZyBwcm9jZWR1cmUgd2lsbCBpbml0YXRlIGEgc2NyZWVuIG9mIGFsbCBwYXJzbmlwLXJlY2lwZSBjb21iaW5hdGlvbnMgc2hvd2luZyBhbiBvdXRwdXQgbGlrZSB0aGlzLiBUaGlzIGlzIGEgZGVtYW5kaW5nIHByb2NlZHVyZSwgYW5kIGhlbmNlIHdoeSB3ZeKAmXZlIGluaXRpYXRlZCBwYXJyYWxsZWwgY29tcHV0ZSB0byBlYXNlIHRoaXMgYWxvbmcuIC0tPg0KDQo8IS0tIFVzaW5nIHRoZSByZXNhbXBsaW5nIHByb2NlZHVyZSwgd29ya2Zsb3dfbWFwIHdpbGwgZml0IGVhY2ggd29ya2Zsb3cgdG8gdGhlIHRyYWluaW5nIGRhdGEgYW5kIGNvbXB1dGUgYWNjdXJhY3kgb24gZWFjaCByZXNhbXBsZSBhbmQgdGFrZSB0aGUgbWVhbi4gRnVydGhlcm1vcmUsIGFzIGVhY2ggb2YgdGhlIHJlY2lwZXMgc3BlY2lmaWVzIHRoYXQgdGhlIGh5cGVycGFyYW1ldGVycyBzaG91bGQgYmUgdHVuZWQsIHdvcmtmbG93X21hcCB3aWxsIHBlcmZvcm0gc29tZSB0dW5pbmcgdG8gcHJvdmlkZSBhbiBvcHRpbWFsIHJlc3VsdC4gLS0+DQoNCjwhLS0gT25jZSBjb21wbGV0ZWQsIHdlIGNhbiBjYWxsIHRoZSBhbGxfd29ya2Zsb3dzIG9iamVjdCBhbmQgY29sbGVjdCBhbGwgdGhlIG1ldHJpY3MgdGhhdCB3ZXJlIGNhbGN1bGF0ZWQgaW4gdGhpcyBwcm9jZWR1cmUsIGFuZCB2aXN1YWxpc2UgdGhlIHJlc3VsdHMuIC0tPg0KPCEtLSBOLkIgdGhlcmUgaXMgYW4gYXV0b3Bsb3QoKSBmdW5jdGlvbiBhdmFpbGFibGUgZm9yIHdvcmtmbG93X3NldCBvYmplY3RzLCBob3dldmVyIHdlIHdhbnQgdG8gYmUgYWJsZSB0byBjb21wYXJlIHJlY2lwZSBhbmQgbW9kZWwgcGVyZm9ybWFuY2Ugc28gaGF2ZSBoYWQgdG8gcmUtZW5naW5lZXIgZmVhdHVyZXMgZm9yIHZpc3VhbGlzYXRpb24gYmVsb3cuIC0tPg0KDQo8IS0tICNWaXN1YWxpc2UgUGVyZm9ybWFuY2UgQ29tcGFyaXNvbiBvZiBXb3JrZmxvd3MgLS0+DQo8IS0tIGNvbGxlY3RfbWV0cmljcyhhbGxfd29ya2Zsb3dzKSAlPiUgIC0tPg0KPCEtLSAgIHNlcGFyYXRlKHdmbG93X2lkLCBpbnRvID0gYygiUmVjaXBlIiwgIk1vZGVsX1R5cGUiKSwgc2VwID0gIl8iLCByZW1vdmUgPSBGLCBleHRyYSA9ICJtZXJnZSIpICU+JSAgLS0+DQo8IS0tICAgZmlsdGVyKC5tZXRyaWMgPT0gImFjY3VyYWN5IikgJT4lICAtLT4NCjwhLS0gICBncm91cF9ieSh3Zmxvd19pZCkgJT4lICAtLT4NCjwhLS0gICBmaWx0ZXIobWVhbiA9PSBtYXgobWVhbikpICU+JSAgLS0+DQo8IS0tICAgZ3JvdXBfYnkobW9kZWwpICU+JSAgLS0+DQo8IS0tICAgc2VsZWN0KC0uY29uZmlnKSAlPiUgIC0tPg0KPCEtLSAgIGRpc3RpbmN0KCkgJT4lIC0tPg0KPCEtLSAgIHVuZ3JvdXAoKSAlPiUgIC0tPg0KPCEtLSAgIG11dGF0ZShXb3JrZmxvd19SYW5rID0gIHJvd19udW1iZXIoLW1lYW4pLCAtLT4NCjwhLS0gICAgICAgICAgLm1ldHJpYyA9IHN0cl90b191cHBlcigubWV0cmljKSkgJT4lIC0tPg0KPCEtLSAgIGdncGxvdChhZXMoeD1Xb3JrZmxvd19SYW5rLCB5ID0gbWVhbiwgc2hhcGUgPSBSZWNpcGUsIGNvbG9yID0gTW9kZWxfVHlwZSkpICsgLS0+DQo8IS0tICAgICBnZW9tX3BvaW50KCkgKyAtLT4NCjwhLS0gICAgIGdlb21fZXJyb3JiYXIoYWVzKHltaW4gPSBtZWFuLXN0ZF9lcnIsIHltYXggPSBtZWFuK3N0ZF9lcnIpKSArIC0tPg0KPCEtLSAgICAgdGhlbWVfbWluaW1hbCgpKyAtLT4NCjwhLS0gICAgIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoKSArIC0tPg0KPCEtLSAgICAgbGFicyh0aXRsZSA9ICJQZXJmb3JtYW5jZSBDb21wYXJpc29uIG9mIFdvcmtmbG93IFNldHMiLCB4ID0gIldvcmtmbG93IFJhbmsiLCB5ID0gIkFjY3VyYWN5IiwgY29sb3IgPSAiTW9kZWwgVHlwZXMiLCBzaGFwZSA9ICJSZWNpcGVzIikgLS0+DQoNCjwhLS0gT3JpZ2luYWwgUmFuZG9tIEZvcmVzdCBNb2RlbCBSZXN1bHRzIC0tPg0KPCEtLSAjVVNJTkcgUkFOR0VSIEVOR0lORSwgMTAwMCBUUkVFUy4gU0FNRSBSRVNVTFQgV0lUSCAxMGsgVFJFRVMsIFJBTkdFUiBFTkdJTkUgLS0+DQo8IS0tICNXSVRIIDEwMDAgVFJFRVMgLS0+DQo8IS0tICMgV2FybmluZzogdHVuZSBjb2x1bW5zIHdlcmUgcmVxdWVzdGVkIGJ1dCB0aGVyZSB3ZXJlIDE2IHByZWRpY3RvcnMgaW4gdGhlIGRhdGEuIDE2IHdpbGwgYmUgdXNlZC4gLS0+DQo8IS0tICMgV2FybmluZzogdHVuZSBzYW1wbGVzIHdlcmUgcmVxdWVzdGVkIGJ1dCB0aGVyZSB3ZXJlIDExNDUyIHJvd3MgaW4gdGhlIGRhdGEuIDExNDUyIHdpbGwgYmUgdXNlZC4gLS0+DQoNCjwhLS0gIyBDYWxsOiAtLT4NCjwhLS0gIyAgcmFuZG9tRm9yZXN0KHggPSBtYXliZV9kYXRhX2ZyYW1lKHgpLCB5ID0geSwgbnRyZWUgPSB+MTAwMCwgbXRyeSA9IG1pbl9jb2xzKH50dW5lKCksICAgICAgeCksIG5vZGVzaXplID0gbWluX3Jvd3MofnR1bmUoKSwgeCksIGltcG9ydGFuY2UgPSB+VFJVRSkgIC0tPg0KPCEtLSAjICAgICAgICAgICAgICAgIFR5cGUgb2YgcmFuZG9tIGZvcmVzdDogY2xhc3NpZmljYXRpb24gLS0+DQo8IS0tICMgICAgICAgICAgICAgICAgICAgICAgTnVtYmVyIG9mIHRyZWVzOiAxMDAwIC0tPg0KPCEtLSAjIE5vLiBvZiB2YXJpYWJsZXMgdHJpZWQgYXQgZWFjaCBzcGxpdDogMTYgLS0+DQo8IS0tICMgIC0tPg0KPCEtLSAjICAgICAgICAgT09CIGVzdGltYXRlIG9mICBlcnJvciByYXRlOiAyMS43MyUgLS0+DQo8IS0tICMgQ29uZnVzaW9uIG1hdHJpeDogLS0+DQo8IS0tICMgICAgICAwICAgIDEgY2xhc3MuZXJyb3IgLS0+DQo8IS0tICMgMCA0ODk5ICA4MjcgICAwLjE0NDQyODkgLS0+DQo8IS0tICMgMSAxNjYyIDQwNjQgICAwLjI5MDI1NTAgLS0+DQoNCjwhLS0gI1dJVEggMTAwMDAgVFJFRVMgLS0+DQoNCjwhLS0gIyBSYW5nZXIgcmVzdWx0IC0tPg0KPCEtLSAjICAtLT4NCjwhLS0gIyBDYWxsOiAtLT4NCjwhLS0gIyAgcmFuZ2VyOjpyYW5nZXIoeCA9IG1heWJlX2RhdGFfZnJhbWUoeCksIHkgPSB5LCBtdHJ5ID0gbWluX2NvbHMofnR1bmUoKSwgICAgICB4KSwgbnVtLnRyZWVzID0gfjEwMDAwLCBtaW4ubm9kZS5zaXplID0gbWluX3Jvd3MofnR1bmUoKSwgICAgICB4KSwgaW1wb3J0YW5jZSA9IH4iaW1wdXJpdHkiLCBudW0udGhyZWFkcyA9IDEsIHZlcmJvc2UgPSBGQUxTRSwgICAgICBzZWVkID0gc2FtcGxlLmludCgxMF41LCAxKSwgcHJvYmFiaWxpdHkgPSBUUlVFKSAgLS0+DQo8IS0tICMgIC0tPg0KPCEtLSAjIFR5cGU6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcm9iYWJpbGl0eSBlc3RpbWF0aW9uICAtLT4NCjwhLS0gIyBOdW1iZXIgb2YgdHJlZXM6ICAgICAgICAgICAgICAgICAgMTAwMDAgIC0tPg0KPCEtLSAjIFNhbXBsZSBzaXplOiAgICAgICAgICAgICAgICAgICAgICAxMTQ1MiAgLS0+DQo8IS0tICMgTnVtYmVyIG9mIGluZGVwZW5kZW50IHZhcmlhYmxlczogIDE4ICAtLT4NCjwhLS0gIyBNdHJ5OiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMTggIC0tPg0KPCEtLSAjIFRhcmdldCBub2RlIHNpemU6ICAgICAgICAgICAgICAgICAxMTQ1MiAgLS0+DQo8IS0tICMgVmFyaWFibGUgaW1wb3J0YW5jZSBtb2RlOiAgICAgICAgIGltcHVyaXR5ICAtLT4NCjwhLS0gIyBTcGxpdHJ1bGU6ICAgICAgICAgICAgICAgICAgICAgICAgZ2luaSAgLS0+DQo8IS0tICMgT09CIHByZWRpY3Rpb24gZXJyb3IgKEJyaWVyIHMuKTogIDAuMjUwMDQzMiAgLS0+DQogIA==